• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2012 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"""A module to support automatic firmware update.
5
6See FirmwareUpdater object below.
7"""
8import array
9import json
10import os
11
12from autotest_lib.client.common_lib import error
13from autotest_lib.client.common_lib.cros import chip_utils
14from autotest_lib.client.common_lib.cros import cros_config
15from autotest_lib.client.cros.faft.utils import flashrom_handler
16
17
18class FirmwareUpdaterError(Exception):
19    """Error in the FirmwareUpdater module."""
20
21
22class FirmwareUpdater(object):
23    """An object to support firmware update.
24
25    This object will create a temporary directory in /usr/local/tmp/faft/autest
26    with two subdirs, keys/ and work/. You can modify the keys in keys/ dir. If
27    you want to provide a given shellball to do firmware update, put shellball
28    under /usr/local/tmp/faft/autest with name chromeos-firmwareupdate.
29
30    @type os_if: autotest_lib.client.cros.faft.utils.os_interface.OSInterface
31    """
32
33    DAEMON = 'update-engine'
34    CBFSTOOL = 'cbfstool'
35    HEXDUMP = 'hexdump -v -e \'1/1 "0x%02x\\n"\''
36
37    DEFAULT_SHELLBALL = '/usr/sbin/chromeos-firmwareupdate'
38    DEFAULT_SUBDIR = 'autest'  # subdirectory of os_interface.state_dir
39    DEFAULT_SECTION_FOR_TARGET = {'bios': 'a', 'ec': 'rw'}
40
41    def __init__(self, os_if):
42        """Initialize the updater tools, but don't load the image data yet."""
43        self.os_if = os_if
44        self._temp_path = self.os_if.state_dir_file(self.DEFAULT_SUBDIR)
45        self._cbfs_work_path = os.path.join(self._temp_path, 'cbfs')
46        self._keys_path = os.path.join(self._temp_path, 'keys')
47        self._work_path = os.path.join(self._temp_path, 'work')
48        self._bios_path = 'bios.bin'
49        self._ec_path = 'ec.bin'
50
51        self.pubkey_path = os.path.join(self._keys_path, 'root_key.vbpubk')
52        self._real_bios_handler = self._create_handler('bios')
53        self._real_ec_handler = self._create_handler('ec')
54        self.initialized = False
55
56    def init(self):
57        """Extract the shellball and other files, unless they already exist."""
58
59        if self.os_if.is_dir(self._work_path):
60            # If work dir is present, assume the whole temp dir is usable as-is.
61            self._detect_image_paths()
62        else:
63            # If work dir is missing, assume the whole temp dir is unusable, and
64            # recreate it.
65            self._create_temp_dir()
66            self.extract_shellball()
67
68        self.initialized = True
69
70    def _get_handler(self, target):
71        """Return the handler for the target, after initializing it if needed.
72
73        @param target: image type ('bios' or 'ec')
74        @return: the handler for that target
75
76        @type target: str
77        @rtype: flashrom_handler.FlashromHandler
78        """
79        if target == 'bios':
80            if not self._real_bios_handler.initialized:
81                bios_file = self._get_image_path('bios')
82                self._real_bios_handler.init(bios_file)
83            return self._real_bios_handler
84        elif target == 'ec':
85            if not self._real_ec_handler.initialized:
86                ec_file = self._get_image_path('ec')
87                self._real_ec_handler.init(ec_file, allow_fallback=True)
88            return self._real_ec_handler
89        else:
90            raise FirmwareUpdaterError("Unhandled target: %r" % target)
91
92    def _create_handler(self, target, suffix=None):
93        """Return a new (not pre-populated) handler for the given target,
94        such as for use in checking installed versions.
95
96        @param target: image type ('bios' or 'ec')
97        @param suffix: additional piece for subdirectory of handler
98                       Example: 'tmp' -> 'autest/<target>.tmp/'
99        @return: a new handler for that target
100
101        @type target: str
102        @rtype: flashrom_handler.FlashromHandler
103        """
104        if suffix:
105            subdir = '%s/%s.%s' % (self.DEFAULT_SUBDIR, target, suffix)
106        else:
107            subdir = '%s/%s' % (self.DEFAULT_SUBDIR, target)
108        return flashrom_handler.FlashromHandler(
109                self.os_if, self.pubkey_path, self._keys_path, target=target,
110                subdir=subdir)
111
112    def _get_image_path(self, target):
113        """Return the handler for the given target
114
115        @param target: image type ('bios' or 'ec')
116        @return: the path of the image file for that target
117
118        @type target: str
119        @rtype: str
120        """
121        if target == 'bios':
122            return os.path.join(self._work_path, self._bios_path)
123        elif target == 'ec':
124            return os.path.join(self._work_path, self._ec_path)
125        else:
126            raise FirmwareUpdaterError("Unhandled target: %r" % target)
127
128    def _get_default_section(self, target):
129        """Return the default section to work with, for the given target
130
131        @param target: image type ('bios' or 'ec')
132        @return: the default section for that target
133
134        @type target: str
135        @rtype: str
136        """
137        if target in self.DEFAULT_SECTION_FOR_TARGET:
138            return self.DEFAULT_SECTION_FOR_TARGET[target]
139        else:
140            raise FirmwareUpdaterError("Unhandled target: %r" % target)
141
142    def _create_temp_dir(self):
143        """Create (or recreate) the temporary directory.
144
145        The default /usr/sbin/chromeos-firmwareupdate is copied into _temp_dir,
146        and devkeys are copied to _key_path. The caller is responsible for
147        extracting the copied shellball.
148        """
149        self.cleanup_temp_dir()
150
151        self.os_if.create_dir(self._temp_path)
152        self.os_if.create_dir(self._cbfs_work_path)
153        self.os_if.create_dir(self._work_path)
154        self.os_if.copy_dir('/usr/share/vboot/devkeys', self._keys_path)
155
156        working_shellball = os.path.join(self._temp_path,
157                                         'chromeos-firmwareupdate')
158        self.os_if.copy_file(self.DEFAULT_SHELLBALL, working_shellball)
159
160    def cleanup_temp_dir(self):
161        """Cleanup temporary directory."""
162        if self.os_if.is_dir(self._temp_path):
163            self.os_if.remove_dir(self._temp_path)
164
165    def stop_daemon(self):
166        """Stop update-engine daemon."""
167        self.os_if.log('Stopping %s...' % self.DAEMON)
168        cmd = 'status %s | grep stop || stop %s' % (self.DAEMON, self.DAEMON)
169        self.os_if.run_shell_command(cmd)
170
171    def start_daemon(self):
172        """Start update-engine daemon."""
173        self.os_if.log('Starting %s...' % self.DAEMON)
174        cmd = 'status %s | grep start || start %s' % (self.DAEMON, self.DAEMON)
175        self.os_if.run_shell_command(cmd)
176
177    def get_ec_hash(self):
178        """Retrieve the hex string of the EC hash."""
179        ec = self._get_handler('ec')
180        return ec.get_section_hash('rw')
181
182    def get_section_fwid(self, target='bios', section=None):
183        """Get one fwid from in-memory image, for the given target.
184
185        @param target: the image type to get from: 'bios (default) or 'ec'
186        @param section: section to return.  Default: A for bios, RW for EC
187
188        @type target: str | None
189        @rtype: str
190        """
191        if section is None:
192            section = self._get_default_section(target)
193        image_path = self._get_image_path(target)
194        if target == 'ec' and not os.path.isfile(image_path):
195            # If the EC image is missing, report a specific error message.
196            raise FirmwareUpdaterError("Shellball does not contain ec.bin")
197
198        handler = self._get_handler(target)
199        handler.new_image(image_path)
200        fwid = handler.get_section_fwid(section)
201        if fwid is not None:
202            return str(fwid)
203        else:
204            return None
205
206    def get_all_fwids(self, target='bios'):
207        """Get all non-empty fwids from in-memory image, for the given target.
208
209        @param target: the image type to get from: 'bios' (default) or 'ec'
210        @return: fwid for the sections
211
212        @type target: str
213        @rtype: dict | None
214        """
215        image_path = self._get_image_path(target)
216        if target == 'ec' and not os.path.isfile(image_path):
217            # If the EC image is missing, report a specific error message.
218            raise FirmwareUpdaterError("Shellball does not contain ec.bin")
219
220        handler = self._get_handler(target)
221        handler.new_image(image_path)
222
223        fwids = {}
224        for section in handler.fv_sections:
225            fwid = handler.get_section_fwid(section)
226            if fwid is not None:
227                fwids[section] = fwid
228        return fwids
229
230    def get_all_installed_fwids(self, target='bios', filename=None):
231        """Get all non-empty fwids from disk or flash, for the given target.
232
233        @param target: the image type to get from: 'bios' (default) or 'ec'
234        @param filename: filename to read instead of using the actual flash
235        @return: fwid for the sections
236
237        @type target: str
238        @type filename: str
239        @rtype: dict
240        """
241        handler = self._create_handler(target, 'installed')
242        if filename:
243            filename = os.path.join(self._temp_path, filename)
244        handler.new_image(filename)
245
246        fwids = {}
247        for section in handler.fv_sections:
248            fwid = handler.get_section_fwid(section)
249            if fwid is not None:
250                fwids[section] = fwid
251        return fwids
252
253    def modify_fwids(self, target='bios', sections=None):
254        """Modify the fwid in the image, but don't flash it.
255
256        @param target: the image type to modify: 'bios' (default) or 'ec'
257        @param sections: section(s) to modify.  Default: A for bios, RW for ec
258        @return: fwids for the modified sections, as {section: fwid}
259
260        @type target: str
261        @type sections: tuple | list
262        @rtype: dict
263        """
264        if sections is None:
265            sections = [self._get_default_section(target)]
266
267        image_fullpath = self._get_image_path(target)
268        if target == 'ec' and not os.path.isfile(image_fullpath):
269            # If the EC image is missing, report a specific error message.
270            raise FirmwareUpdaterError("Shellball does not contain ec.bin")
271
272        handler = self._get_handler(target)
273        fwids = handler.modify_fwids(sections)
274
275        handler.dump_whole(image_fullpath)
276        handler.new_image(image_fullpath)
277
278        return fwids
279
280    def modify_ecid_and_flash_to_bios(self):
281        """Modify ecid, put it to AP firmware, and flash it to the system.
282
283        This method is used for testing EC software sync for EC EFS (Early
284        Firmware Selection). It creates a slightly different EC RW image
285        (a different EC fwid) in AP firmware, in order to trigger EC
286        software sync on the next boot (a different hash with the original
287        EC RW).
288
289        The steps of this method:
290         * Modify the EC fwid by appending a '~', like from
291           'fizz_v1.1.7374-147f1bd64' to 'fizz_v1.1.7374-147f1bd64~'.
292         * Resign the EC image.
293         * Store the modififed EC RW image to CBFS component 'ecrw' of the
294           AP firmware's FW_MAIN_A and FW_MAIN_B, and also the new hash.
295         * Resign the AP image.
296         * Flash the modified AP image back to the system.
297        """
298        self.cbfs_setup_work_dir()
299
300        fwid = self.get_section_fwid('ec', 'rw')
301        if fwid.endswith('~'):
302            raise FirmwareUpdaterError('The EC fwid is already modified')
303
304        # Modify the EC FWID and resign
305        fwid = fwid[:-1] + '~'
306        ec = self._get_handler('ec')
307        ec.set_section_fwid('rw', fwid)
308        ec.resign_ec_rwsig()
309
310        # Replace ecrw to the new one
311        ecrw_bin_path = os.path.join(self._cbfs_work_path,
312                                     chip_utils.ecrw.cbfs_bin_name)
313        ec.dump_section_body('rw', ecrw_bin_path)
314
315        # Replace ecrw.hash to the new one
316        ecrw_hash_path = os.path.join(self._cbfs_work_path,
317                                      chip_utils.ecrw.cbfs_hash_name)
318        with open(ecrw_hash_path, 'w') as f:
319            f.write(self.get_ec_hash())
320
321        # Store the modified ecrw and its hash to cbfs
322        self.cbfs_replace_chip(chip_utils.ecrw.fw_name, extension='')
323
324        # Resign and flash the AP firmware back to the system
325        self.cbfs_sign_and_flash()
326
327    def corrupt_diagnostics_image(self, local_filename):
328        """Corrupts a diagnostics image in the CBFS working directory.
329
330        @param local_filename: Filename for storing the diagnostics image in the
331            CBFS working directory
332        """
333        local_path = os.path.join(self._cbfs_work_path, local_filename)
334
335        # Invert the last few bytes of the image. Note that cbfstool will
336        # silently ignore bytes added after the end of the ELF, and it will
337        # refuse to use an ELF with noticeably corrupted headers as a payload.
338        num_bytes = 4
339        with open(local_path, 'rb+') as image:
340            image.seek(-num_bytes, os.SEEK_END)
341            last_bytes = array.array('B')
342            last_bytes.fromfile(image, num_bytes)
343
344            for i in range(len(last_bytes)):
345                last_bytes[i] = last_bytes[i] ^ 0xff
346
347            image.seek(-num_bytes, os.SEEK_END)
348            last_bytes.tofile(image)
349
350    def resign_firmware(self, version=None, work_path=None):
351        """Resign firmware with version.
352
353        Args:
354            version: new firmware version number, default to no modification.
355            work_path: work path, default to the updater work path.
356        """
357        if work_path is None:
358            work_path = self._work_path
359        self.os_if.run_shell_command(
360                '/usr/share/vboot/bin/resign_firmwarefd.sh '
361                '%s %s %s %s %s %s %s %s' %
362                (os.path.join(work_path, self._bios_path),
363                 os.path.join(self._temp_path, 'output.bin'),
364                 os.path.join(self._keys_path, 'firmware_data_key.vbprivk'),
365                 os.path.join(self._keys_path, 'firmware.keyblock'),
366                 os.path.join(self._keys_path,
367                              'dev_firmware_data_key.vbprivk'),
368                 os.path.join(self._keys_path, 'dev_firmware.keyblock'),
369                 os.path.join(self._keys_path, 'kernel_subkey.vbpubk'),
370                 ('%d' % version) if version is not None else ''))
371        self.os_if.copy_file(
372                '%s' % os.path.join(self._temp_path, 'output.bin'),
373                '%s' % os.path.join(work_path, self._bios_path))
374
375    def _read_manifest(self, shellball=None):
376        """This gets the manifest from the shellball or the extracted directory.
377
378        @param shellball: Path of the shellball to read from (via --manifest).
379                          If None (default), read from extracted manifest.json.
380        @return: the manifest information, or None
381
382        @type shellball: str | None
383        @rtype: dict
384        """
385
386        if shellball:
387            output = self.os_if.run_shell_command_get_output(
388                    'sh %s --manifest' % shellball)
389            manifest_text = '\n'.join(output or [])
390        else:
391            manifest_file = os.path.join(self._work_path, 'manifest.json')
392            manifest_text = self.os_if.read_file(manifest_file)
393
394        if manifest_text:
395            return json.loads(manifest_text)
396        else:
397            # TODO(dgoyette): Perhaps raise an exception for empty manifest?
398            return None
399
400    def _detect_image_paths(self, shellball=None):
401        """Scans shellball manifest to find correct bios and ec image paths.
402
403        @param shellball: Path of the shellball to read from (via --manifest).
404                          If None (default), read from extracted manifest.json.
405        @type shellball: str | None
406        """
407        model_name = cros_config.call_cros_config_get_output(
408                '/ name', self.os_if.run_shell_command_get_result)
409
410        if not model_name:
411            return
412
413        manifest = self._read_manifest(shellball)
414
415        if manifest:
416            model_info = manifest.get(model_name)
417            if model_info:
418
419                try:
420                    self._bios_path = model_info['host']['image']
421                except KeyError:
422                    pass
423
424                try:
425                    self._ec_path = model_info['ec']['image']
426                except KeyError:
427                    pass
428
429    def extract_shellball(self, append=None):
430        """Extract the working shellball.
431
432        Args:
433            append: decide which shellball to use with format
434                chromeos-firmwareupdate-[append]. Use 'chromeos-firmwareupdate'
435                if append is None.
436        Returns:
437            string: the full path of the shellball
438        """
439        working_shellball = os.path.join(self._temp_path,
440                                         'chromeos-firmwareupdate')
441        if append:
442            working_shellball = working_shellball + '-%s' % append
443
444        self.os_if.run_shell_command(
445                'sh %s --unpack %s' % (working_shellball, self._work_path))
446
447        # use the json file that was extracted, to catch extraction problems.
448        self._detect_image_paths()
449        return working_shellball
450
451    def repack_shellball(self, append=None):
452        """Repack shellball with new fwid.
453
454        New fwid follows the rule: [orignal_fwid]-[append].
455
456        Args:
457            append: save the new shellball with a suffix, for example,
458                chromeos-firmwareupdate-[append]. Use 'chromeos-firmwareupdate'
459                if append is None.
460        Returns:
461            string: The full path to the shellball
462        """
463
464        working_shellball = os.path.join(self._temp_path,
465                                         'chromeos-firmwareupdate')
466        if append:
467            new_shellball = working_shellball + '-%s' % append
468            self.os_if.copy_file(working_shellball, new_shellball)
469            working_shellball = new_shellball
470
471        self.os_if.run_shell_command(
472                'sh %s --repack %s' % (working_shellball, self._work_path))
473
474        # use the shellball that was repacked, to catch repacking problems.
475        self._detect_image_paths(working_shellball)
476        return working_shellball
477
478    def reset_shellball(self):
479        """Extract shellball, then revert the AP and EC handlers' data."""
480        self._create_temp_dir()
481        self.extract_shellball()
482        self.reload_images()
483
484    def reload_images(self):
485        """Reload handlers from the on-disk images, in case they've changed."""
486        bios_file = os.path.join(self._work_path, self._bios_path)
487        self._real_bios_handler.deinit()
488        self._real_bios_handler.init(bios_file)
489        if self._real_ec_handler.is_available():
490            ec_file = os.path.join(self._work_path, self._ec_path)
491            self._real_ec_handler.deinit()
492            self._real_ec_handler.init(ec_file, allow_fallback=True)
493
494    def run_firmwareupdate(self, mode, append=None, options=None):
495        """Do firmwareupdate with updater in temp_dir.
496
497        @param append: decide which shellball to use with format
498                chromeos-firmwareupdate-[append].
499                Use'chromeos-firmwareupdate' if append is None.
500        @param mode: ex.'autoupdate', 'recovery', 'bootok', 'factory_install'...
501        @param options: ex. ['--noupdate_ec', '--force'] or [] or None.
502
503        @type append: str
504        @type mode: str
505        @type options: list | tuple | None
506        """
507        if mode == 'bootok':
508            # Since CL:459837, bootok is moved to chromeos-setgoodfirmware.
509            set_good_cmd = '/usr/sbin/chromeos-setgoodfirmware'
510            if os.path.isfile(set_good_cmd):
511                return self.os_if.run_shell_command_get_status(set_good_cmd)
512
513        updater = os.path.join(self._temp_path, 'chromeos-firmwareupdate')
514        if append:
515            updater = '%s-%s' % (updater, append)
516
517        if options is None:
518            options = []
519        if isinstance(options, tuple):
520            options = list(options)
521
522        def _has_emulate(option):
523            return option == '--emulate' or option.startswith('--emulate=')
524
525        if self.os_if.test_mode and not filter(_has_emulate, options):
526            # if in test mode, forcibly use --emulate, if not already used.
527            fake_bios = os.path.join(self._temp_path, 'rpc-test-fake-bios.bin')
528            if not os.path.exists(fake_bios):
529                bios_reader = self._create_handler('bios', 'tmp')
530                bios_reader.dump_flash(fake_bios)
531            options = ['--emulate', fake_bios] + options
532
533        update_cmd = '/bin/sh %s --mode %s %s' % (updater, mode,
534                                                  ' '.join(options))
535
536        return self.os_if.run_shell_command_get_status(update_cmd)
537
538    def cbfs_setup_work_dir(self):
539        """Sets up cbfs on DUT.
540
541        Finds bios.bin on the DUT and sets up a temp dir to operate on
542        bios.bin.  If a bios.bin was specified, it is copied to the DUT
543        and used instead of the native bios.bin.
544
545        Returns:
546            The cbfs work directory path.
547        """
548
549        self.os_if.remove_dir(self._cbfs_work_path)
550        self.os_if.copy_dir(self._work_path, self._cbfs_work_path)
551
552        return self._cbfs_work_path
553
554    def cbfs_extract_chip(self, fw_name, extension='.bin'):
555        """Extracts chip firmware blob from cbfs.
556
557        For a given chip type, looks for the corresponding firmware
558        blob and hash in the specified bios.  The firmware blob and
559        hash are extracted into self._cbfs_work_path.
560
561        The extracted blobs will be <fw_name><extension> and
562        <fw_name>.hash located in cbfs_work_path.
563
564        Args:
565            fw_name: Chip firmware name to be extracted.
566            extension: Extension of the name of the cbfs component.
567
568        Returns:
569            Boolean success status.
570        """
571
572        bios = os.path.join(self._cbfs_work_path, self._bios_path)
573        fw = fw_name
574        cbfs_extract = '%s %s extract -r FW_MAIN_A -n %s%%s -f %s%%s' % (
575                self.CBFSTOOL, bios, fw, os.path.join(self._cbfs_work_path,
576                                                      fw))
577
578        cmd = cbfs_extract % (extension, extension)
579        if self.os_if.run_shell_command_get_status(cmd) != 0:
580            return False
581
582        cmd = cbfs_extract % ('.hash', '.hash')
583        if self.os_if.run_shell_command_get_status(cmd) != 0:
584            return False
585
586        return True
587
588    def cbfs_extract_diagnostics(self, diag_name, local_filename):
589        """Runs cbfstool to extract a diagnostics image.
590
591        @param diag_name: Name of the diagnostics image in CBFS
592        @param local_filename: Filename for storing the diagnostics image in the
593            CBFS working directory
594        """
595        bios_path = os.path.join(self._cbfs_work_path, self._bios_path)
596        cbfs_extract = '%s %s extract -m x86 -r RW_LEGACY -n %s -f %s' % (
597                self.CBFSTOOL, bios_path, diag_name,
598                os.path.join(self._cbfs_work_path, local_filename))
599
600        self.os_if.run_shell_command(cbfs_extract)
601
602    def cbfs_get_chip_hash(self, fw_name):
603        """Returns chip firmware hash blob.
604
605        For a given chip type, returns the chip firmware hash blob.
606        Before making this request, the chip blobs must have been
607        extracted from cbfs using cbfs_extract_chip().
608        The hash data is returned as hexadecimal string.
609
610        @param fw_name: Chip firmware name whose hash blob to get.
611        @return: Boolean success status.
612        @raise error.CmdError: Underlying remote shell operations failed.
613        """
614
615        hexdump_cmd = '%s %s.hash' % (
616                self.HEXDUMP, os.path.join(self._cbfs_work_path, fw_name))
617        hashblob = self.os_if.run_shell_command_get_output(hexdump_cmd)
618        return hashblob
619
620    def cbfs_replace_chip(self, fw_name, extension='.bin'):
621        """Replaces chip firmware in CBFS (bios.bin).
622
623        For a given chip type, replaces its firmware blob and hash in
624        bios.bin.  All files referenced are expected to be in the
625        directory set up using cbfs_setup_work_dir().
626
627        @param fw_name: Chip firmware name to be replaced.
628        @param extension: Extension of the name of the cbfs component.
629        @return: Boolean success status.
630        @raise error.CmdError: Underlying remote shell operations failed.
631        """
632
633        bios = os.path.join(self._cbfs_work_path, self._bios_path)
634        rm_hash_cmd = '%s %s remove -r FW_MAIN_A,FW_MAIN_B -n %s.hash' % (
635                self.CBFSTOOL, bios, fw_name)
636        rm_bin_cmd = '%s %s remove -r FW_MAIN_A,FW_MAIN_B -n %s%s' % (
637                self.CBFSTOOL, bios, fw_name, extension)
638        expand_cmd = '%s %s expand -r FW_MAIN_A,FW_MAIN_B' % (self.CBFSTOOL,
639                                                              bios)
640        add_hash_cmd = ('%s %s add -r FW_MAIN_A,FW_MAIN_B -t raw -c none '
641                        '-f %s.hash -n %s.hash') % (
642                                self.CBFSTOOL, bios,
643                                os.path.join(self._cbfs_work_path,
644                                             fw_name), fw_name)
645        add_bin_cmd = ('%s %s add -r FW_MAIN_A,FW_MAIN_B -t raw -c lzma '
646                       '-f %s%s -n %s%s') % (
647                               self.CBFSTOOL, bios,
648                               os.path.join(self._cbfs_work_path, fw_name),
649                               extension, fw_name, extension)
650        truncate_cmd = '%s %s truncate -r FW_MAIN_A,FW_MAIN_B' % (
651                self.CBFSTOOL, bios)
652
653        self.os_if.run_shell_command(rm_hash_cmd)
654        self.os_if.run_shell_command(rm_bin_cmd)
655        try:
656            self.os_if.run_shell_command(expand_cmd)
657        except error.CmdError:
658            self.os_if.log(
659                    ('%s may be too old, '
660                     'continuing without "expand" support') % self.CBFSTOOL)
661
662        self.os_if.run_shell_command(add_hash_cmd)
663        self.os_if.run_shell_command(add_bin_cmd)
664        try:
665            self.os_if.run_shell_command(truncate_cmd)
666        except error.CmdError:
667            self.os_if.log(
668                    ('%s may be too old, '
669                     'continuing without "truncate" support') % self.CBFSTOOL)
670
671        return True
672
673    def cbfs_replace_diagnostics(self, diag_name, local_filename):
674        """Runs cbfstool to replace a diagnostics image in the firmware image.
675
676        @param diag_name: Name of the diagnostics image in CBFS
677        @param local_filename: Filename for storing the diagnostics image in the
678            CBFS working directory
679        """
680        bios_path = os.path.join(self._cbfs_work_path, self._bios_path)
681        rm_cmd = '%s %s remove -r RW_LEGACY -n %s' % (
682                self.CBFSTOOL, bios_path, diag_name)
683        expand_cmd = '%s %s expand -r RW_LEGACY' % (self.CBFSTOOL, bios_path)
684        add_cmd = ('%s %s add-payload -r RW_LEGACY -c lzma -n %s -f %s') % (
685                self.CBFSTOOL, bios_path, diag_name,
686                os.path.join(self._cbfs_work_path, local_filename))
687        truncate_cmd = '%s %s truncate -r RW_LEGACY' % (
688                self.CBFSTOOL, bios_path)
689
690        self.os_if.run_shell_command(rm_cmd)
691
692        try:
693            self.os_if.run_shell_command(expand_cmd)
694        except error.CmdError:
695            self.os_if.log(
696                    '%s may be too old, continuing without "expand" support'
697                    % self.CBFSTOOL)
698
699        self.os_if.run_shell_command(add_cmd)
700
701        try:
702            self.os_if.run_shell_command(truncate_cmd)
703        except error.CmdError:
704            self.os_if.log(
705                    '%s may be too old, continuing without "truncate" support'
706                    % self.CBFSTOOL)
707
708    def cbfs_sign_and_flash(self):
709        """Signs CBFS (bios.bin) and flashes it."""
710        self.resign_firmware(work_path=self._cbfs_work_path)
711        bios = self._get_handler('bios')
712        bios.new_image(os.path.join(self._cbfs_work_path, self._bios_path))
713        bios.write_whole()
714        return True
715
716    def copy_bios(self, filename):
717        """Copy the shellball BIOS to the given name in the temp dir
718
719        @param filename: the filename to use for the copy
720        @return: the full path of the BIOS
721
722        @type filename: str
723        @rtype: str
724        """
725        if not isinstance(filename, basestring):
726            raise FirmwareUpdaterError(
727                    "Filename must be a string: %s" % repr(filename))
728        src_bios = os.path.join(self._work_path, self._bios_path)
729        dst_bios = os.path.join(self._temp_path, filename)
730        self.os_if.copy_file(src_bios, dst_bios)
731        return dst_bios
732
733    def get_temp_path(self):
734        """Get temp directory path."""
735        return self._temp_path
736
737    def get_keys_path(self):
738        """Get keys directory path."""
739        return self._keys_path
740
741    def get_work_path(self):
742        """Get work directory path."""
743        return self._work_path
744
745    def get_bios_relative_path(self):
746        """Gets the relative path of the bios image in the shellball."""
747        return self._bios_path
748
749    def get_ec_relative_path(self):
750        """Gets the relative path of the ec image in the shellball."""
751        return self._ec_path
752
753    def get_image_gbb_flags(self, filename=None):
754        """Get the GBB flags in the given image (shellball image if unspecified)
755
756        @param filename: the image path to act on (None to use shellball image)
757        @return: An integer of the GBB flags.
758        """
759        if filename:
760            filename = os.path.join(self._temp_path, filename)
761            handler = self._create_handler('bios', 'image')
762            handler.new_image(filename)
763        else:
764            handler = self._get_handler('bios')
765        return handler.get_gbb_flags()
766
767    def set_image_gbb_flags(self, flags, filename=None):
768        """Set the GBB flags in the given image (shellball image if unspecified)
769
770        @param flags: the flags to set
771        @param filename: the image path to act on (None to use shellball image)
772
773        @type flags: int
774        @type filename: str | None
775        """
776        if filename:
777            filename = os.path.join(self._temp_path, filename)
778            handler = self._create_handler('bios', 'image')
779            handler.new_image(filename)
780        else:
781            filename = self._get_image_path('bios')
782            handler = self._get_handler('bios')
783        handler.set_gbb_flags(flags)
784        handler.dump_whole(filename)
785