gooftool: Keep simple __init__ to avoid dependency cycle.
When importing modules, Python need to solve all modules included by
__init__. Previously we put the main implementation of Gooftool in its
__init__.py, which makes modules interacting with Gooftool (i.e., hwid)
will have problem due to dependency cycle.
To prevent that, we should follow Python guide line to make __init__ a
simple zero dummy file and move the implementation details to a new
'core.py', with the command line and command interface (gooftool.py)
into 'commands.py'.
This helps resolving dependency issues and makes it easier for people to
figure out the difference between 'gooftool/__init__.py#Gooftool' and
'gooftool/gooftool.py'.
BUG=none
TEST=make test; gooftool
Change-Id: If207bcb6e42bf9a99954c2be23d40e39c13bbfae
Reviewed-on: https://chromium-review.googlesource.com/329489
Commit-Ready: Hung-Te Lin <hungte@chromium.org>
Tested-by: Hung-Te Lin <hungte@chromium.org>
Reviewed-by: Wei-Han Chen <stimim@chromium.org>
diff --git a/py/gooftool/commands.py b/py/gooftool/commands.py
new file mode 100755
index 0000000..4743126
--- /dev/null
+++ b/py/gooftool/commands.py
@@ -0,0 +1,1034 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# pylint: disable=E1101
+
+"""Google Factory Tool.
+
+This tool is indended to be used on factory assembly lines. It
+provides all of the Google required test functionality and must be run
+on each device as part of the assembly process.
+"""
+
+import collections
+import logging
+import os
+import pipes
+import re
+import sys
+import threading
+import time
+import xmlrpclib
+import yaml
+
+from tempfile import gettempdir
+
+import factory_common # pylint: disable=W0611
+
+from cros.factory.gooftool import crosfw
+from cros.factory.gooftool import report_upload
+from cros.factory.gooftool.core import Gooftool
+from cros.factory.gooftool.common import Shell
+from cros.factory.gooftool.probe import Probe, PROBEABLE_COMPONENT_CLASSES
+from cros.factory.gooftool.probe import ReadRoVpd, ReadRwVpd
+from cros.factory.gooftool.probe import CalculateFirmwareHashes
+from cros.factory.gooftool.vpd_data import KNOWN_VPD_FIELD_DATA
+from cros.factory.hacked_argparse import CmdArg, Command, ParseCmdline
+from cros.factory.hacked_argparse import verbosity_cmd_arg
+from cros.factory.hwid.v2 import hwid_tool
+from cros.factory.hwid.v2.yaml_datastore import YamlWrite
+from cros.factory.hwid.v3 import common
+from cros.factory.hwid.v3 import hwid_utils
+from cros.factory.test import event_log
+from cros.factory.test.env import paths
+from cros.factory.test.factory import FACTORY_LOG_PATH
+from cros.factory.test.rules.privacy import FilterDict
+from cros.factory.utils import file_utils
+from cros.factory.utils.debug_utils import SetupLogging
+from cros.factory.utils.process_utils import Spawn
+from cros.factory.utils.type_utils import Error
+
+
+# TODO(tammo): Replace calls to sys.exit with raise Exit, and maybe
+# treat that specially (as a smoot exit, as opposed to the more
+# verbose output for generic Error).
+
+_global_gooftool = None
+_gooftool_lock = threading.Lock()
+
+
+def GetGooftool(options):
+ global _global_gooftool # pylint: disable=W0603
+
+ if _global_gooftool is None:
+ with _gooftool_lock:
+ hwid_version = getattr(options, 'hwid_version', 3)
+ if hwid_version == 2:
+ hwdb_path = getattr(options, 'hwdb_path', None)
+ component_db = (
+ hwid_tool.HardwareDb(options.hwdb_path).comp_db if hwdb_path
+ else None)
+ _global_gooftool = Gooftool(hwid_version=2, component_db=component_db)
+ elif hwid_version == 3:
+ board = getattr(options, 'board', None)
+ hwdb_path = getattr(options, 'hwdb_path', None)
+ _global_gooftool = Gooftool(hwid_version=3, board=board,
+ hwdb_path=hwdb_path)
+ else:
+ raise Error, 'Invalid HWID version: %r' % options.hwid_version
+
+ return _global_gooftool
+
+
+@Command('write_hwid',
+ CmdArg('hwid', metavar='HWID', help='HWID string'))
+def WriteHWID(options):
+ """Write specified HWID value into the system BB."""
+
+ logging.info('writing hwid string %r', options.hwid)
+ GetGooftool(options).WriteHWID(options.hwid)
+ event_log.Log('write_hwid', hwid=options.hwid)
+ print 'Wrote HWID: %r' % options.hwid
+
+
+_board_cmd_arg = CmdArg(
+ '--board', metavar='BOARD',
+ default=None, help='Board name to test.')
+
+_hwdb_path_cmd_arg = CmdArg(
+ '--hwdb_path', metavar='PATH',
+ default=common.DEFAULT_HWID_DATA_PATH,
+ help='Path to the HWID database.')
+
+_hwid_status_list_cmd_arg = CmdArg(
+ '--status', nargs='*', default=['supported'],
+ help='allow only HWIDs with these status values')
+
+_probe_results_cmd_arg = CmdArg(
+ '--probe_results', metavar='RESULTS.yaml',
+ help=('Output from "gooftool probe --include_vpd" (used instead of '
+ 'probing this system).'))
+
+_device_info_cmd_arg = CmdArg(
+ '--device_info', metavar='DEVICE_INFO.yaml', default=None,
+ help='A dict of device info to use instead of fetching from shopfllor '
+ 'server.')
+
+_hwid_cmd_arg = CmdArg(
+ '--hwid', metavar='HWID',
+ help='HWID to verify (instead of the currently set HWID of this system).')
+
+_rma_mode_cmd_arg = CmdArg(
+ '--rma_mode', action='store_true',
+ help='Enable RMA mode, do not check for deprecated components.')
+
+_cros_core_cmd_arg = CmdArg(
+ '--cros_core', action='store_true',
+ help='Finalize for ChromeOS Core devices (may add or remove few test '
+ 'items. For example, branding verification or firmware bitmap '
+ 'locale settings).')
+
+_hwid_version_cmd_arg = CmdArg(
+ '-i', '--hwid-version', default=3, choices=(2, 3), type=int,
+ help='Version of HWID to operate on. (default: %(default)s)')
+
+_enforced_release_channels_cmd_arg = CmdArg(
+ '--enforced_release_channels', nargs='*', default=None,
+ help='Enforced release image channels.')
+
+
+@Command('best_match_hwids',
+ _hwdb_path_cmd_arg,
+ CmdArg('-b', '--board', metavar='BOARD',
+ help='optional BOARD name, needed only if data is present '
+ 'for more than one'),
+ CmdArg('--bom', metavar='BOM', help='BOM name'),
+ CmdArg('--variant', metavar='VARIANT', help='VARIANT code'),
+ CmdArg('--optimistic', action='store_true',
+ help='do not probe; assume singletons match'),
+ CmdArg('--comps', nargs='*', default=[],
+ help='list of canonical component names'),
+ CmdArg('--missing', nargs='*', default=[],
+ help='list component classes to be assumed missing'),
+ CmdArg('--status', nargs='*', default=['supported'],
+ help='consider only HWIDs within this list of status values'))
+def BestMatchHwids(options):
+ """Determine a list of possible HWIDs using provided args and probing.
+
+ VOLATILE can always be determined by probing. To get a unique
+ result, VARIANT must be specified for all cases where the matching
+ BOM has more than one associated variant code, otherwise all HWID
+ variants will be returned. Both VARIANT and BOM information can
+ alternatively be specified using the --stdin_comps argument, which
+ allows specifying a list of canonical names (one per line) on stdin,
+ one per line. Based on what is known from BOM and stdin_comps,
+ determine a list of components to probe for, and use those probe
+ results to resolve a list of matching HWIDs. If no boms,
+ components, or variant codes are specified, then a list of all HWIDs
+ that match probeable components will be returned.
+
+ Returns (on stdout): A list of HWIDs that match the available probe
+ results and argument contraints, one per line.
+
+ Example:
+
+ // Three ways to specify a keyboard (assuming it is a variant component)
+ gooftool best_match_hwids --missing keyboard
+ gooftool best_match_hwids --variant A or
+ gooftool best_match_hwids --comps us_kbd
+ """
+
+ map(hwid_tool.Validate.Status, options.status)
+ hw_db = hwid_tool.HardwareDb(options.hwdb_path)
+ comp_db = hw_db.comp_db
+ device = hw_db.GetDevice(options.board)
+ component_spec = hwid_tool.ComponentSpec.New()
+ if options.bom:
+ device.BomExists(options.bom)
+ component_spec = hwid_tool.CombineComponentSpecs(
+ component_spec, device.boms[options.bom].primary)
+ if options.variant:
+ device.VariantExists(options.variant)
+ variant_spec = device.variants[options.variant]
+ if hwid_tool.ComponentSpecsConflict(component_spec, variant_spec):
+ sys.exit('ERROR: multiple specifications for these components:\n%s'
+ % YamlWrite(sorted(
+ hwid_tool.ComponentSpecClasses(component_spec) &
+ hwid_tool.ComponentSpecClasses(variant_spec))))
+ component_spec = hwid_tool.CombineComponentSpecs(
+ component_spec, variant_spec)
+ if options.comps or options.missing:
+ map(comp_db.CompExists, options.comps)
+ map(comp_db.CompClassExists, options.missing)
+ extra_comp_spec = comp_db.CreateComponentSpec(
+ components=options.comps,
+ missing=options.missing)
+ print 'cmdline asserted components:\n%s' % extra_comp_spec.Encode()
+ if hwid_tool.ComponentSpecsConflict(component_spec, extra_comp_spec):
+ sys.exit('ERROR: multiple specifications for these components:\n%s'
+ % YamlWrite(sorted(
+ hwid_tool.ComponentSpecClasses(component_spec) &
+ hwid_tool.ComponentSpecClasses(extra_comp_spec))))
+ component_spec = hwid_tool.CombineComponentSpecs(
+ component_spec, extra_comp_spec)
+ spec_classes = hwid_tool.ComponentSpecClasses(component_spec)
+ missing_classes = set(comp_db.all_comp_classes) - spec_classes
+ if missing_classes and not options.optimistic:
+ non_probeable_missing = missing_classes - PROBEABLE_COMPONENT_CLASSES
+ if non_probeable_missing:
+ sys.exit('FAILURE: these classes are necessary, were not specified '
+ 'as inputs, and cannot be probed for:\n%s'
+ 'This problem can often be addressed by specifying all of '
+ 'the missing components on the command line (see the command '
+ 'help).' % YamlWrite(list(non_probeable_missing)))
+ print 'probing for missing classes:'
+ print YamlWrite(list(missing_classes))
+ probe_results = Probe(target_comp_classes=list(missing_classes),
+ probe_volatile=False, probe_initial_config=False)
+ cooked_components = comp_db.MatchComponentProbeValues(
+ probe_results.found_probe_value_map)
+ if cooked_components.unmatched:
+ sys.exit('ERROR: some probed components are unrecognized:\n%s'
+ % YamlWrite(cooked_components.unmatched))
+ probed_comp_spec = comp_db.CreateComponentSpec(
+ components=cooked_components.matched,
+ missing=probe_results.missing_component_classes)
+ component_spec = hwid_tool.CombineComponentSpecs(
+ component_spec, probed_comp_spec)
+ print YamlWrite({'component data used for matching': {
+ 'missing component classes': component_spec.classes_missing,
+ 'found components': component_spec.components}})
+ component_data = hwid_tool.ComponentData(
+ extant_components=hwid_tool.ComponentSpecCompClassMap(
+ component_spec).keys(),
+ classes_missing=component_spec.classes_missing)
+ match_tree = device.BuildMatchTree(component_data)
+ if not match_tree:
+ sys.exit('FAILURE: NO matching BOMs found')
+ print 'potential BOMs/VARIANTs:'
+ potential_variants = set()
+ potential_volatiles = set()
+ for bom_name, variant_tree in match_tree.items():
+ print ' BOM: %-8s VARIANTS: %s' % (
+ bom_name, ', '.join(sorted(variant_tree)))
+ for variant_code in variant_tree:
+ potential_variants.add(variant_code)
+ for volatile_code in device.volatiles:
+ status = device.GetHwidStatus(bom_name, variant_code, volatile_code)
+ if status in options.status:
+ potential_volatiles.add(volatile_code)
+ print ''
+ if len(potential_variants) == 0:
+ sys.exit('FAILURE: no matching VARIANTs found')
+ if len(potential_volatiles) == 0:
+ sys.exit('FAILURE: no VOLATILEs found for potential matching BOMs/VARIANTS '
+ '(with specified status)')
+ if (options.optimistic and
+ len(match_tree) == 1 and
+ len(potential_variants) == 1 and
+ len(potential_volatiles) == 1):
+ print ('MATCHING HWID: %s' % device.FmtHwid(match_tree.keys().pop(),
+ potential_variants.pop(),
+ potential_volatiles.pop()))
+ return
+ print ('probing VOLATILEs to resolve potential matches: %s\n' %
+ ', '.join(sorted(potential_volatiles)))
+ vol_probe_results = Probe(
+ target_comp_classes=[],
+ probe_volatile=True,
+ probe_initial_config=False)
+ cooked_volatiles = device.MatchVolatileValues(
+ vol_probe_results.found_volatile_values)
+ match_tree = device.BuildMatchTree(
+ component_data, cooked_volatiles.matched_tags)
+ matched_hwids = device.GetMatchTreeHwids(match_tree)
+ if matched_hwids:
+ for hwid in matched_hwids:
+ if matched_hwids[hwid] in options.status:
+ print 'MATCHING HWID: %s' % hwid
+ return
+ print 'exact HWID matching failed, but the following BOMs match: %s' % (
+ ', '.join(sorted(match_tree)))
+ if options.optimistic and len(match_tree) == 1:
+ bom_name = set(match_tree).pop()
+ bom = device.boms[bom_name]
+ variant_matches = match_tree[bom_name]
+ if len(variant_matches) == 1:
+ var_code = set(variant_matches).pop()
+ elif len(bom.variants) == 1:
+ var_code = set(bom.variants).pop()
+ else:
+ sys.exit('FAILURE: NO matching HWIDs found; optimistic matching failed '
+ 'because there were too many variants to choose from for BOM %r'
+ % bom_name)
+ hwids = [device.FmtHwid(bom_name, var_code, vol_code)
+ for vol_code in device.volatiles
+ if device.GetHwidStatus(bom_name, var_code, vol_code)
+ in options.status]
+ for hwid in hwids:
+ print 'MATCHING HWID: %s' % hwid
+ return
+ else:
+ print ('optimistic matching not attempted because either it was '
+ 'not requested, or because the number of BOMs was <> 1\n')
+ sys.exit('FAILURE: NO matching HWIDs found')
+
+
+@Command('probe',
+ CmdArg('--comps', nargs='*',
+ help='List of keys from the component_db registry.'),
+ CmdArg('--fast_fw_probe', action='store_true',
+ help='Do a fast probe for EC and main firmware versions only. '
+ 'This implies --no_vol and --no_ic.'),
+ CmdArg('--no_vol', action='store_true',
+ help='Do not probe volatile data.'),
+ CmdArg('--no_ic', action='store_true',
+ help='Do not probe initial_config data.'),
+ CmdArg('--include_vpd', action='store_true',
+ help='Include VPD data in volatiles.'))
+def RunProbe(options):
+ """Print yaml-formatted breakdown of probed device properties."""
+ print GetGooftool(options).Probe(
+ target_comp_classes=options.comps,
+ fast_fw_probe=options.fast_fw_probe,
+ probe_volatile=not options.no_vol,
+ probe_initial_config=not options.no_ic,
+ probe_vpd=options.include_vpd).Encode()
+
+
+@Command('verify_components',
+ _hwdb_path_cmd_arg,
+ CmdArg('target_comps', nargs='*'))
+def VerifyComponents(options):
+ """Verify that probeable components all match entries in the component_db.
+
+ Probe for each component class in the target_comps and verify
+ that a corresponding match exists in the component_db -- make sure
+ that these components are present, that they have been approved, but
+ do not check against any specific BOM/HWID configurations.
+ """
+
+ try:
+ result = GetGooftool(options).VerifyComponents(
+ options.target_comps)
+ except ValueError, e:
+ sys.exit(e)
+
+ PrintVerifyComponentsResults(result)
+
+
+def PrintVerifyComponentsResults(result):
+ """Prints out the results of VerifyComponents method call.
+
+ Groups the results into two groups: 'matches' and 'errors', and prints out
+ their values.
+ """
+ # group by matches and errors
+ matches = []
+ errors = []
+ for result_list in result.values():
+ for component_name, _, error in result_list:
+ if component_name:
+ matches.append(component_name)
+ else:
+ errors.append(error)
+
+ if matches:
+ print 'found probeable components:\n %s' % '\n '.join(matches)
+ if errors:
+ print '\nerrors:\n %s' % '\n '.join(errors)
+ sys.exit('\ncomponent verification FAILURE')
+ else:
+ print '\ncomponent verification SUCCESS'
+
+
+@Command('verify_hwid_v2',
+ _hwid_status_list_cmd_arg,
+ _hwdb_path_cmd_arg,
+ _probe_results_cmd_arg,
+ _hwid_cmd_arg)
+def VerifyHWIDv2(options):
+ """Verify system HWID properties match probed device properties.
+
+ First probe components, volatile and initial_config parameters for
+ the DUT. Then use the available device data to produce a list of
+ candidate HWIDs. Then verify the HWID from the DUT is present in
+ that list. Then verify that the DUT initial config values match
+ those specified for its HWID. Finally, verify that VPD contains all
+ the necessary fields as specified by the board data, and when
+ possible verify that values are legitimate.
+ """
+ def VerifyVpd(ro_vpd_keys, rw_vpd_keys):
+ for key in ro_vpd_keys:
+ if key not in ro_vpd:
+ sys.exit('Missing required RO VPD field: %s' % key)
+ known_valid_values = KNOWN_VPD_FIELD_DATA.get(key, None)
+ value = ro_vpd[key]
+ if (known_valid_values is not None) and (value not in known_valid_values):
+ sys.exit('Invalid RO VPD entry : key %r, value %r' % (key, value))
+ for key in rw_vpd_keys:
+ if key not in rw_vpd:
+ sys.exit('Missing required RW VPD field: %s' % key)
+ known_valid_values = KNOWN_VPD_FIELD_DATA.get(key, None)
+ value = rw_vpd[key]
+ if (known_valid_values is not None) and (value not in known_valid_values):
+ sys.exit('Invalid RW VPD entry : key %r, value %r' % (key, value))
+ event_log.Log('vpd', ro_vpd=FilterDict(ro_vpd), rw_vpd=FilterDict(rw_vpd))
+ map(hwid_tool.Validate.Status, options.status)
+
+ if options.hwid:
+ hwid_str = options.hwid
+ else:
+ main_fw_file = crosfw.LoadMainFirmware().GetFileName()
+ gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
+ hwid_str = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
+ hwid = hwid_tool.ParseHwid(hwid_str)
+ hw_db = hwid_tool.HardwareDb(options.hwdb_path)
+ print 'Verifying HWID: %r\n' % hwid.hwid
+ device = hw_db.GetDevice(hwid.board)
+ hwid_status = device.GetHwidStatus(hwid.bom, hwid.variant, hwid.volatile)
+ if hwid_status not in options.status:
+ sys.exit('HWID status must be one of [%s], found %r' %
+ (', '.join(options.status), hwid_status))
+ if options.probe_results:
+ # Pull in probe results (including VPD data) from the given file
+ # rather than probing the current system.
+ probe_results = hwid_tool.ProbeResults.Decode(
+ open(options.probe_results).read())
+ ro_vpd = {}
+ rw_vpd = {}
+ for k, v in probe_results.found_volatile_values.items():
+ match = re.match(r'^vpd\.(ro|rw)\.(\w+)$', k)
+ if match:
+ del probe_results.found_volatile_values[k]
+ (ro_vpd if match.group(1) == 'ro' else rw_vpd)[match.group(2)] = v
+ else:
+ probe_results = Probe()
+ ro_vpd = ReadRoVpd()
+ rw_vpd = ReadRwVpd()
+ cooked_components = hw_db.comp_db.MatchComponentProbeValues(
+ probe_results.found_probe_value_map)
+ cooked_volatiles = device.MatchVolatileValues(
+ probe_results.found_volatile_values)
+ cooked_initial_configs = device.MatchInitialConfigValues(
+ probe_results.initial_configs)
+ component_data = hwid_tool.ComponentData(
+ extant_components=cooked_components.matched,
+ classes_missing=probe_results.missing_component_classes)
+ match_tree = device.BuildMatchTree(
+ component_data, cooked_volatiles.matched_tags)
+ matched_hwids = device.GetMatchTreeHwids(match_tree)
+ print 'HWID status: %s\n' % hwid_status
+ print 'probed system components:'
+ print YamlWrite(cooked_components.__dict__)
+ print 'missing component classes:'
+ print YamlWrite(probe_results.missing_component_classes)
+ print 'probed volatiles:'
+ print YamlWrite(cooked_volatiles.__dict__)
+ print 'probed initial_configs:'
+ print YamlWrite(cooked_initial_configs)
+ print 'hwid match tree:'
+ print YamlWrite(match_tree)
+ event_log.Log(
+ 'probe',
+ found_components=cooked_components.__dict__,
+ missing_component_classes=probe_results.missing_component_classes,
+ volatiles=cooked_volatiles.__dict__,
+ initial_configs=cooked_initial_configs)
+ if hwid.hwid not in matched_hwids:
+ err_msg = 'HWID verification FAILED.\n'
+ if cooked_components.unmatched:
+ sys.exit(err_msg + 'some components could not be indentified:\n%s' %
+ YamlWrite(cooked_components.unmatched))
+ if not match_tree:
+ sys.exit(err_msg + 'no matching boms were found for components:\n%s' %
+ component_data.Encode())
+ if hwid.bom not in match_tree:
+ sys.exit(err_msg + 'matching boms [%s] do not include target bom %r' %
+ (', '.join(sorted(match_tree)), hwid.bom))
+ err_msg += 'target bom %r matches components' % hwid.bom
+ if hwid.bom not in device.IntersectBomsAndInitialConfigs(
+ cooked_initial_configs):
+ sys.exit(err_msg + ', but failed initial config verification')
+ matched_variants = match_tree.get(hwid.bom, {})
+ if hwid.variant not in matched_variants:
+ sys.exit(err_msg + ', but target variant_code %r did not match' %
+ hwid.variant)
+ matched_volatiles = matched_variants.get(hwid.variant, {})
+ if hwid.volatile not in matched_volatiles:
+ sys.exit(err_msg + ', but target volatile_code %r did not match' %
+ hwid.volatile)
+ found_status = matched_volatiles.get(hwid.volatile, None)
+ sys.exit(err_msg + ', but hwid status %r was unacceptable' % found_status)
+ VerifyVpd(device.vpd_ro_fields, device.vpd_rw_fields)
+ event_log.Log('verified_hwid', hwid=hwid)
+ print 'Verification SUCCESS!'
+
+
+@Command('verify_keys')
+def VerifyKeys(options): # pylint: disable=W0613
+ """Verify keys in firmware and SSD match."""
+
+ return GetGooftool(options).VerifyKeys()
+
+
+@Command('set_fw_bitmap_locale')
+def SetFirmwareBitmapLocale(options): # pylint: disable=W0613
+ """Use VPD locale value to set firmware bitmap default language."""
+
+ (index, locale) = GetGooftool(options).SetFirmwareBitmapLocale()
+ logging.info('Firmware bitmap initial locale set to %d (%s).',
+ index, locale)
+
+
+@Command('verify_system_time')
+def VerifySystemTime(options): # pylint: disable=W0613
+ """Verify system time is later than release filesystem creation time."""
+
+ return GetGooftool(options).VerifySystemTime()
+
+
+@Command('verify_rootfs')
+def VerifyRootFs(options): # pylint: disable=W0613
+ """Verify rootfs on SSD is valid by checking hash."""
+
+ return GetGooftool(options).VerifyRootFs()
+
+
+@Command('verify_tpm')
+def VerifyTPM(options): # pylint: disable=W0613
+ """Verify TPM is cleared."""
+
+ return GetGooftool(options).VerifyTPM()
+
+
+@Command('verify_me_locked')
+def VerifyManagementEngineLocked(options): # pylint: disable=W0613
+ """Verify Managment Engine is locked."""
+
+ return GetGooftool(options).VerifyManagementEngineLocked()
+
+
+@Command('verify_switch_wp')
+def VerifyWPSwitch(options): # pylint: disable=W0613
+ """Verify hardware write protection switch is enabled."""
+
+ GetGooftool(options).VerifyWPSwitch()
+
+
+@Command('verify_switch_dev')
+def VerifyDevSwitch(options): # pylint: disable=W0613
+ """Verify developer switch is disabled."""
+
+ if GetGooftool(options).CheckDevSwitchForDisabling():
+ logging.warn('VerifyDevSwitch: No physical switch.')
+ event_log.Log('switch_dev', type='virtual switch')
+
+
+@Command('verify_branding')
+def VerifyBranding(options): # pylint: disable=W0613
+ """Verify that branding fields are properly set.
+
+ customization_id, if set in the RO VPD, must be of the correct format.
+
+ rlz_brand_code must be set either in the RO VPD or OEM partition, and must
+ be of the correct format.
+ """
+ return GetGooftool(options).VerifyBranding()
+
+
+@Command('verify_release_channel',
+ _enforced_release_channels_cmd_arg)
+def VerifyReleaseChannel(options): # pylint: disable=W0613
+ """Verify that release image channel is correct.
+
+ ChromeOS has four channels: canary, dev, beta and stable.
+ The last three channels support image auto-updates, checks
+ that release image channel is one of them.
+ """
+ return GetGooftool(options).VerifyReleaseChannel(
+ options.enforced_release_channels)
+
+
+@Command('write_protect')
+def EnableFwWp(options): # pylint: disable=W0613
+ """Enable then verify firmware write protection."""
+
+ def CalculateLegacyRange(fw_type, length, section_data,
+ section_name):
+ ro_size = length / 2
+ ro_a = int(section_data[0] / ro_size)
+ ro_b = int((section_data[0] + section_data[1] - 1) / ro_size)
+ if ro_a != ro_b:
+ raise Error('%s firmware section %s has illegal size' %
+ (fw_type, section_name))
+ ro_offset = ro_a * ro_size
+ return (ro_offset, ro_size)
+
+ def WriteProtect(fw_file_path, fw_type, legacy_section):
+ """Calculate protection size, then invoke flashrom.
+
+ Our supported chips only allow write protecting half their total
+ size, so we parition the flash chipset space accordingly.
+ """
+
+ raw_image = open(fw_file_path, 'rb').read()
+ wp_section = 'WP_RO'
+ image = crosfw.FirmwareImage(raw_image)
+ if image.has_section(wp_section):
+ section_data = image.get_section_area(wp_section)
+ ro_offset = section_data[0]
+ ro_size = section_data[1]
+ elif image.has_section(legacy_section):
+ section_data = image.get_section_area(legacy_section)
+ (ro_offset, ro_size) = CalculateLegacyRange(
+ fw_type, len(raw_image), section_data, legacy_section)
+ else:
+ raise Error('could not find %s firmware section %s or %s' %
+ (fw_type, wp_section, legacy_section))
+
+ logging.debug('write protecting %s [off=%x size=%x]', fw_type,
+ ro_offset, ro_size)
+ crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, ro_size)
+
+ WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
+ event_log.Log('wp', fw='main')
+ ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
+ if ec_fw_file is not None:
+ WriteProtect(ec_fw_file, 'ec', 'EC_RO')
+ event_log.Log('wp', fw='ec')
+ else:
+ logging.warning('EC not write protected (seems there is no EC flash).')
+
+
+@Command('clear_gbb_flags')
+def ClearGBBFlags(options): # pylint: disable=W0613
+ """Zero out the GBB flags, in preparation for transition to release state.
+
+ No GBB flags are set in release/shipping state, but they are useful
+ for factory/development. See "gbb_utility --flags" for details.
+ """
+
+ GetGooftool(options).ClearGBBFlags()
+ event_log.Log('clear_gbb_flags')
+
+
+@Command('clear_factory_vpd_entries')
+def ClearFactoryVPDEntries(options): # pylint: disable=W0613
+ """Clears factory.* items in the RW VPD."""
+ entries = GetGooftool(options).ClearFactoryVPDEntries()
+ event_log.Log('clear_factory_vpd_entries', entries=FilterDict(entries))
+
+
+@Command('generate_stable_device_secret')
+def GenerateStableDeviceSecret(options): # pylint: disable=W0613
+ """Generates a a fresh stable device secret and stores it in the RO VPD."""
+ GetGooftool(options).GenerateStableDeviceSecret()
+ event_log.Log('generate_stable_device_secret')
+
+_cutoff_args_cmd_arg = CmdArg(
+ '--cutoff_args',
+ help='Battery cutoff arguments to be passed to battery_cutoff.sh '
+ 'after wiping. Should be the following format: '
+ '[--method shutdown|reboot|battery_cutoff|battery_cutoff_at_shutdown] '
+ '[--check-ac connect_ac|remove_ac] '
+ '[--min-battery-percent <minimum battery percentage>] '
+ '[--max-battery-percent <maximum battery percentage>] '
+ '[--min-battery-voltage <minimum battery voltage>] '
+ '[--max-battery-voltage <maximum battery voltage>]')
+_shopfloor_url_args_cmd_arg = CmdArg(
+ '--shopfloor_url',
+ help='Shopfloor server url to be informed when in-place wipe is done. '
+ 'After in-place wipe, a XML-RPC request will be sent to the '
+ 'given url to indicate the completion of wipe.')
+@Command('wipe_in_place',
+ CmdArg('--fast', action='store_true',
+ help='use non-secure but faster wipe method.'),
+ _cutoff_args_cmd_arg,
+ _shopfloor_url_args_cmd_arg)
+def WipeInPlace(options):
+ """Start factory wipe directly without reboot."""
+
+ GetGooftool(options).WipeInPlace(options.fast, options.cutoff_args,
+ options.shopfloor_url)
+
+@Command('prepare_wipe',
+ CmdArg('--fast', action='store_true',
+ help='use non-secure but faster wipe method.'))
+def PrepareWipe(options):
+ """Prepare system for transition to release state in next reboot."""
+
+ GetGooftool(options).PrepareWipe(options.fast)
+
+
+@Command('verify',
+ CmdArg('--no_write_protect', action='store_true',
+ help='Do not check write protection switch state.'),
+ _hwid_version_cmd_arg,
+ _hwid_status_list_cmd_arg,
+ _hwdb_path_cmd_arg,
+ _board_cmd_arg,
+ _probe_results_cmd_arg,
+ _hwid_cmd_arg,
+ _rma_mode_cmd_arg,
+ _cros_core_cmd_arg,
+ _enforced_release_channels_cmd_arg)
+def Verify(options):
+ """Verifies if whole factory process is ready for finalization.
+
+ This routine performs all the necessary checks to make sure the
+ device is ready to be finalized, but does not modify state. These
+ checks include dev switch, firmware write protection switch, hwid,
+ system time, keys, and root file system.
+ """
+
+ if not options.no_write_protect:
+ VerifyWPSwitch(options)
+ VerifyManagementEngineLocked(options)
+ VerifyDevSwitch(options)
+ if options.hwid_version == 2:
+ VerifyHWIDv2(options)
+ elif options.hwid_version == 3:
+ VerifyHWIDv3(options)
+ else:
+ raise Error, 'Invalid HWID version: %r' % options.hwid_version
+ VerifySystemTime(options)
+ VerifyKeys(options)
+ VerifyRootFs(options)
+ VerifyTPM(options)
+ if options.cros_core:
+ logging.info('VerifyBranding is skipped for ChromeOS Core device.')
+ else:
+ VerifyBranding(options)
+ VerifyReleaseChannel(options)
+
+
+@Command('untar_stateful_files')
+def UntarStatefulFiles(unused_options):
+ """Untars stateful files from stateful_files.tar.xz on stateful partition.
+
+ If that file does not exist (which should only be R30 and earlier),
+ this is a no-op.
+ """
+ tar_file = os.path.join(paths.DEVICE_STATEFUL_PATH, 'stateful_files.tar.xz')
+ if os.path.exists(tar_file):
+ Spawn(['tar', 'xf', tar_file], cwd=paths.DEVICE_STATEFUL_PATH,
+ log=True, check_call=True)
+ else:
+ logging.warning('No stateful files at %s', tar_file)
+
+
+@Command('log_source_hashes')
+def LogSourceHashes(options): # pylint: disable=W0613
+ """Logs hashes of source files in the factory toolkit."""
+ # WARNING: The following line is necessary to validate the integrity
+ # of the factory software. Do not remove or modify it.
+ #
+ # 警告:此行会验证工厂软件的完整性,禁止删除或修改。
+ event_log.Log(
+ 'source_hashes',
+ **file_utils.HashSourceTree(os.path.join(paths.FACTORY_PATH, 'py')))
+
+
+@Command('log_system_details')
+def LogSystemDetails(options): # pylint: disable=W0613
+ """Write miscellaneous system details to the event log."""
+
+ event_log.Log('system_details', **GetGooftool(options).GetSystemDetails())
+
+
+def CreateReportArchiveBlob(*args, **kwargs):
+ """Creates a report archive and returns it as a blob.
+
+ Args:
+ See CreateReportArchive.
+
+ Returns:
+ An xmlrpclib.Binary object containing a .tar.xz file.
+ """
+ with open(CreateReportArchive(*args, **kwargs)) as f:
+ return xmlrpclib.Binary(f.read())
+
+
+def CreateReportArchive(device_sn=None, add_file=None):
+ """Creates a report archive in a temporary directory.
+
+ Args:
+ device_sn: The device serial number (optional).
+ add_file: A list of files to add (optional).
+
+ Returns:
+ Path to the archive.
+ """
+ def NormalizeAsFileName(token):
+ return re.sub(r'\W+', '', token).strip()
+
+ target_name = '%s%s.tar.xz' % (
+ time.strftime('%Y%m%dT%H%M%SZ',
+ time.gmtime()),
+ ('' if device_sn is None else
+ '_' + NormalizeAsFileName(device_sn)))
+ target_path = os.path.join(gettempdir(), target_name)
+
+ # Intentionally ignoring dotfiles in EVENT_LOG_DIR.
+ tar_cmd = 'cd %s ; tar cJf %s *' % (event_log.EVENT_LOG_DIR, target_path)
+ tar_cmd += ' --add-file %s' % FACTORY_LOG_PATH
+ if add_file:
+ for f in add_file:
+ # Require absolute paths since the tar command may change the
+ # directory.
+ if not f.startswith('/'):
+ raise Error('Not an absolute path: %s' % f)
+ if not os.path.exists(f):
+ raise Error('File does not exist: %s' % f)
+ tar_cmd += ' --add-file %s' % pipes.quote(f)
+ cmd_result = Shell(tar_cmd)
+
+ if ((cmd_result.status == 1) and
+ all((x == '' or
+ 'file changed as we read it' in x or
+ "Removing leading `/' from member names" in x)
+ for x in cmd_result.stderr.split('\n'))):
+ # That's OK. Make sure it's valid though.
+ Spawn(['tar', 'tfJ', target_path], check_call=True, log=True,
+ ignore_stdout=True)
+ elif not cmd_result.success:
+ raise Error('unable to tar event logs, cmd %r failed, stderr: %r' %
+ (tar_cmd, cmd_result.stderr))
+
+ return target_path
+
+_upload_method_cmd_arg = CmdArg(
+ '--upload_method', metavar='METHOD:PARAM',
+ help=('How to perform the upload. METHOD should be one of '
+ '{ftp, shopfloor, ftps, cpfe}.'))
+_add_file_cmd_arg = CmdArg(
+ '--add_file', metavar='FILE', action='append',
+ help='Extra file to include in report (must be an absolute path)')
+
+
+@Command('upload_report',
+ _upload_method_cmd_arg,
+ _add_file_cmd_arg)
+def UploadReport(options):
+ """Create a report containing key device details."""
+ ro_vpd = ReadRoVpd()
+ device_sn = ro_vpd.get('serial_number', None)
+ if device_sn is None:
+ logging.warning('RO_VPD missing device serial number')
+ device_sn = 'MISSING_SN_' + event_log.TimedUuid()
+ target_path = CreateReportArchive(device_sn)
+
+ if options.upload_method is None or options.upload_method == 'none':
+ logging.warning('REPORT UPLOAD SKIPPED (report left at %s)', target_path)
+ return
+ method, param = options.upload_method.split(':', 1)
+ if method == 'shopfloor':
+ report_upload.ShopFloorUpload(target_path, param)
+ elif method == 'ftp':
+ report_upload.FtpUpload(target_path, 'ftp:' + param)
+ elif method == 'ftps':
+ report_upload.CurlUrlUpload(target_path, '--ftp-ssl-reqd ftp:%s' % param)
+ elif method == 'cpfe':
+ report_upload.CpfeUpload(target_path, pipes.quote(param))
+ else:
+ raise Error('unknown report upload method %r', method)
+
+
+@Command('finalize',
+ CmdArg('--no_write_protect', action='store_true',
+ help='Do not enable firmware write protection.'),
+ CmdArg('--fast', action='store_true',
+ help='use non-secure but faster wipe method.'),
+ CmdArg('--wipe_in_place', action='store_true',
+ help='Start factory wiping in place without reboot.'),
+ _cutoff_args_cmd_arg,
+ _shopfloor_url_args_cmd_arg,
+ _hwid_version_cmd_arg,
+ _hwdb_path_cmd_arg,
+ _hwid_status_list_cmd_arg,
+ _upload_method_cmd_arg,
+ _add_file_cmd_arg,
+ _board_cmd_arg,
+ _probe_results_cmd_arg,
+ _hwid_cmd_arg,
+ _rma_mode_cmd_arg,
+ _cros_core_cmd_arg,
+ _enforced_release_channels_cmd_arg)
+def Finalize(options):
+ """Verify system readiness and trigger transition into release state.
+
+ This routine does the following:
+ - Verifies system state (see verify command)
+ - Untars stateful_files.tar.xz, if it exists, in the stateful partition, to
+ initialize files such as the CRX cache
+ - Modifies firmware bitmaps to match locale
+ - Clears all factory-friendly flags from the GBB
+ - Removes factory-specific entries from RW_VPD (factory.*)
+ - Enables firmware write protection (cannot rollback after this)
+ - Uploads system logs & reports
+ - Sets the necessary boot flags to cause wipe of the factory image on the
+ next boot.
+ """
+ Verify(options)
+ LogSourceHashes(options)
+ UntarStatefulFiles(options)
+ if options.cros_core:
+ logging.info('SetFirmwareBitmapLocale is skipped for ChromeOS Core device.')
+ else:
+ SetFirmwareBitmapLocale(options)
+ ClearGBBFlags(options)
+ ClearFactoryVPDEntries(options)
+ GenerateStableDeviceSecret(options)
+ if options.no_write_protect:
+ logging.warn('WARNING: Firmware Write Protection is SKIPPED.')
+ event_log.Log('wp', fw='both', status='skipped')
+ else:
+ EnableFwWp({})
+ LogSystemDetails(options)
+ UploadReport(options)
+ if options.wipe_in_place:
+ event_log.Log('wipe_in_place')
+ WipeInPlace(options)
+ else:
+ PrepareWipe(options)
+
+
+def VerifyHWIDv3(options):
+ """A simple wrapper that calls out to HWID utils to verify version 3 HWID.
+
+ This is mainly for Gooftool to verify v3 HWID during finalize. For testing
+ and development purposes, please use `hwid` command.
+ """
+ db = GetGooftool(options).db
+ encoded_string = options.hwid or hwid_utils.GetHWIDString()
+ if options.probe_results:
+ probed_results = yaml.load(open(options.probe_results).read())
+ else:
+ probed_results = yaml.load(Probe(probe_vpd=True).Encode())
+ vpd = hwid_utils.GetVPD(probed_results)
+
+ event_log.Log('probed_results', probed_results=FilterDict(probed_results))
+ event_log.Log('vpd', vpd=FilterDict(vpd))
+
+ hwid_utils.VerifyHWID(db, encoded_string, probed_results, vpd,
+ rma_mode=options.rma_mode)
+
+ event_log.Log('verified_hwid', hwid=encoded_string)
+
+
+def ParseDecodedHWID(hwid):
+ """Parse the HWID object into a more compact dict.
+
+ Args:
+ hwid: A decoded HWID object.
+
+ Returns:
+ A dict containing the board name, the binary string, and the list of
+ components.
+ """
+ results = {}
+ results['board'] = hwid.database.board
+ results['binary_string'] = hwid.binary_string
+ results['components'] = collections.defaultdict(list)
+ components = hwid.bom.components
+ for comp_cls in sorted(components):
+ for (comp_name, probed_values, _) in sorted(components[comp_cls]):
+ if not probed_values:
+ db_components = hwid.database.components
+ probed_values = db_components.GetComponentAttributes(
+ comp_cls, comp_name).get('values')
+ results['components'][comp_cls].append(
+ {comp_name: probed_values if probed_values else None})
+ # Convert defaultdict to dict.
+ results['components'] = dict(results['components'])
+ return results
+
+
+@Command('get_firmware_hash',
+ CmdArg('--file', metavar='FILE', help='Firmware File.'))
+def GetFirmwareHash(options):
+ """Get firmware hash from a file"""
+ if os.path.exists(options.file):
+ hashes = CalculateFirmwareHashes(options.file)
+ for section, value_dict in hashes.iteritems():
+ print '%s:' % section
+ for key, value in value_dict.iteritems():
+ print ' %s: %s' % (key, value)
+ else:
+ raise Error('File does not exist: %s' % options.file)
+
+
+def Main():
+ """Run sub-command specified by the command line args."""
+
+ options = ParseCmdline(
+ ('Perform Google required factory tests. All the HWID-related functions '
+ 'provided here are mainly for the deprecated HWID v2. To access HWID '
+ 'v3-related utilities, please use `hwid` command.'),
+ CmdArg('-l', '--log', metavar='PATH',
+ help='Write logs to this file.'),
+ CmdArg('--suppress-event-logs', action='store_true',
+ help='Suppress event logging.'),
+ verbosity_cmd_arg)
+ SetupLogging(options.verbosity, options.log)
+ event_log.SetGlobalLoggerDefaultPrefix('gooftool')
+ event_log.GetGlobalLogger().suppress = options.suppress_event_logs
+ logging.debug('gooftool options: %s', repr(options))
+ try:
+ logging.debug('GOOFTOOL command %r', options.command_name)
+ options.command(options)
+ logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
+ except Error, e:
+ logging.exception(e)
+ sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
+ except Exception, e:
+ logging.exception(e)
+ sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
+
+
+if __name__ == '__main__':
+ Main()