blob: c6468c41719e46918d0f753d45c140b3329831d6 [file] [log] [blame]
#!/bin/env python
#
# Copyright (c) 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.
"""Command-line interface for HWID v3 utilities."""
import json
import logging
import os
import shutil
import yaml
import factory_common # pylint: disable=W0611
from cros.factory.hwid.v3 import common
from cros.factory.hwid.v3 import rule
from cros.factory.hwid.v3 import database
from cros.factory.hwid.v3 import hwid_utils
from cros.factory.test import state
from cros.factory.test.rules import phase
from cros.factory.tools import build_board
from cros.factory.utils.argparse_utils import CmdArg
from cros.factory.utils.argparse_utils import Command
from cros.factory.utils.argparse_utils import ParseCmdline
from cros.factory.utils import sys_utils
from cros.factory.utils import yaml_utils
from cros.factory.utils import process_utils
_COMMON_ARGS = [
CmdArg('-p', '--hwid-db-path', default=None,
help='path to the HWID database directory'),
CmdArg('-b', '--board', default=None,
help=('board name of the HWID database to load.\n'
'(required if not running on a DUT)')),
CmdArg('-v', '--verbose', default=False, action='store_true',
help='enable verbose output'),
CmdArg('--no-verify-checksum', default=False, action='store_true',
help='do not check database checksum'),
CmdArg('--phase', default=None,
help=('override phase for phase checking (defaults to the current '
'as returned by the "factory phase" command)')),
]
_DATABASE_BUILDER_COMMON_ARGS = [
CmdArg('--add-default-component', default=None,
nargs='+', metavar='COMP', dest='add_default_comp',
help='Component classes that add a default item.\n'),
CmdArg('--add-null-component', default=None,
nargs='+', metavar='COMP', dest='add_null_comp',
help='Component classes that add a null item.\n'),
CmdArg('--del-component', default=None,
nargs='+', metavar='COMP', dest='del_comp',
help='Component classes that is deleted from database.\n'),
CmdArg('--region', default=None, nargs='+',
help='Supported regions'),
CmdArg('--chassis', default=None, nargs='+',
help='Supported chassis identifiers')
]
class Arg(object):
"""A simple class to store arguments passed to the add_argument method of
argparse module.
"""
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
@Command(
'build-database',
CmdArg('--probed-results-file', default=None, required=True,
help='a file with probed results.\n'),
CmdArg('--image-id', default='EVT',
help="Name of image_id. Default is 'EVT'\n"),
*_DATABASE_BUILDER_COMMON_ARGS)
def BuildDatabaseWrapper(options):
'''Build the HWID database from probed result.'''
if not os.path.isfile(options.probed_results_file):
raise IOError('File %s is not found.' % options.probed_results_file)
if not os.path.isdir(options.hwid_db_path):
raise IOError('%s is not is directory.' % options.hwid_db_path)
yaml_utils.ParseMappingAsOrderedDict()
probed_results = hwid_utils.GetProbedResults(options.probed_results_file)
database_path = os.path.join(options.hwid_db_path, options.board.upper())
hwid_utils.BuildDatabase(
database_path, probed_results, options.board, options.image_id,
options.add_default_comp, options.add_null_comp, options.del_comp,
options.region, options.chassis)
logging.info('Output the database to %s', database_path)
@Command(
'update-database',
CmdArg('--probed-results-file', default=None,
help='a file with probed results.\n'),
CmdArg('--image-id', default=None,
help="Name of image_id.\n"),
CmdArg('--output-database', default=None,
help='Write into different file.\n'),
*_DATABASE_BUILDER_COMMON_ARGS)
def UpdateDatabaseWrapper(options):
'''Update the HWID database from probed result.'''
if options.probed_results_file is None:
probed_results = None
else:
if not os.path.isfile(options.probed_results_file):
raise IOError('File %s is not found.' % options.probed_results_file)
probed_results = hwid_utils.GetProbedResults(options.probed_results_file)
old_db_path = os.path.join(options.hwid_db_path, options.board.upper())
if options.output_database is None:
# If the output path is not assigned, we update the database in place.
# We backup the original database before update.
bak_db_path = old_db_path + '.bak'
logging.info('In-place update, backup the database to %s', bak_db_path)
shutil.copyfile(old_db_path, bak_db_path)
# Load the original database as OrderedDict
yaml_utils.ParseMappingAsOrderedDict()
logging.info('Load the orignal database from %s', old_db_path)
with open(old_db_path, 'r') as f:
old_db = yaml.load(f)
database_path = options.output_database or old_db_path
hwid_utils.UpdateDatabase(
database_path, probed_results, old_db, options.image_id,
options.add_default_comp, options.add_null_comp, options.del_comp,
options.region, options.chassis)
logging.info('Output the updated database to %s.', database_path)
@Command(
'generate',
CmdArg('--probed-results-file', default=None,
help=('a file with probed results.\n'
'(required if not running on a DUT)')),
CmdArg('--device-info-file', default=None,
help=('a file with device info.\n'
'(required if not running on a DUT.)\n'
'example content of this file:\n'
' component.antenna: ACN\n'
' component.has_cellular: True\n'
' component.keyboard: US_API\n')),
CmdArg('--rma-mode', default=False, action='store_true',
help='whether to enable RMA mode'),
CmdArg('--json-output', default=False, action='store_true',
help='whether to dump result in JSON format'))
def GenerateHWIDWrapper(options):
"""Generates HWID."""
probed_results = hwid_utils.GetProbedResults(options.probed_results_file)
# Select right device info (from file or shopfloor).
if options.device_info_file:
device_info = hwid_utils.GetDeviceInfo(options.device_info_file)
elif sys_utils.InChroot():
raise ValueError('Cannot get device info from shopfloor in chroot. '
'Please specify device info with an input file. If you '
'are running with command-line, use --device-info-file')
else:
device_info = state.GetDeviceData()
vpd = hwid_utils.GetVPD(probed_results)
verbose_output = {
'device_info': device_info,
'probed_results': probed_results,
'vpd': vpd
}
logging.debug(yaml.dump(verbose_output, default_flow_style=False))
hwid = hwid_utils.GenerateHWID(options.database, probed_results, device_info,
vpd, options.rma_mode)
if options.json_output:
print json.dumps({
'encoded_string': hwid.encoded_string,
'binary_string': hwid.binary_string,
'hwdb_checksum': hwid.database.checksum})
else:
print 'Encoded HWID string: %s' % hwid.encoded_string
print 'Binary HWID string: %s' % hwid.binary_string
@Command(
'decode',
CmdArg('hwid', nargs='?', default=None,
help='the HWID to decode.\n(required if not running on a DUT)'))
def DecodeHWIDWrapper(options):
"""Decodes HWID."""
encoded_string = options.hwid if options.hwid else hwid_utils.GetHWIDString()
decoded_hwid = hwid_utils.DecodeHWID(options.database, encoded_string)
print yaml.dump(hwid_utils.ParseDecodedHWID(decoded_hwid),
default_flow_style=False)
@Command(
'verify',
CmdArg('hwid', nargs='?', default=None,
help='the HWID to verify.\n(required if not running on a DUT)'),
CmdArg('--probed-results-file', default=None,
help=('a file with probed results.\n'
'(required if not running on a DUT)')),
CmdArg('--rma-mode', default=False, action='store_true',
help='whether to enable RMA mode.'))
def VerifyHWIDWrapper(options):
"""Verifies HWID."""
encoded_string = options.hwid if options.hwid else hwid_utils.GetHWIDString()
probed_results = hwid_utils.GetProbedResults(options.probed_results_file)
vpd = hwid_utils.GetVPD(probed_results)
hwid_utils.VerifyHWID(options.database, encoded_string, probed_results, vpd,
options.rma_mode, options.phase)
# No exception raised. Verification was successful.
print 'Verification passed.'
@Command(
'verify-components',
CmdArg('--probed-results-file', default=None,
help=('a file with probed results.\n'
'(required if not running on a DUT)')),
CmdArg('--json_output', action='store_true', default=False,
help='Output the returned value in json format.'),
CmdArg('-c', '--components', default=None,
help='the list of component classes to verify'),
CmdArg('--no-fast-fw-probe', dest='fast_fw_probe', action='store_false',
default=True, help='probe only firmware and EC version strings'))
def VerifyComponentsWrapper(options):
"""Verifies components."""
redirect_stdout = process_utils.DummyFile() if options.json_output else None
with process_utils.RedirectStandardStreams(stdout=redirect_stdout):
if not options.components:
probed_results = hwid_utils.GetProbedResults(
infile=options.probed_results_file,
fast_fw_probe=options.fast_fw_probe)
else:
options.components = [v.strip() for v in options.components.split(',')]
if set(['ro_ec_firmware', 'ro_main_firmware']) & set(options.components):
probe_volatile = True
else:
probe_volatile = False
probed_results = hwid_utils.GetProbedResults(
infile=options.probed_results_file,
target_comp_classes=options.components,
fast_fw_probe=options.fast_fw_probe,
probe_volatile=probe_volatile, probe_initial_config=False)
result = hwid_utils.VerifyComponents(options.database, probed_results,
options.components)
if options.json_output:
def _ConvertToDict(obj):
if isinstance(obj, (common.ProbedComponentResult, rule.Value)):
return _ConvertToDict(obj.__dict__)
if isinstance(obj, list):
return [_ConvertToDict(item) for item in obj]
if isinstance(obj, tuple):
return tuple([_ConvertToDict(item) for item in obj])
if isinstance(obj, dict):
return {key: _ConvertToDict(value) for key, value in obj.iteritems()}
return obj
new_result = _ConvertToDict(result)
print json.dumps(new_result)
else:
failed = []
waive_list = []
if options.fast_fw_probe:
waive_list = ['key_recovery', 'key_root', 'hash_gbb']
for comp_cls, comps in result.iteritems():
if comp_cls in waive_list:
continue
for comp_result in comps:
if comp_result.error:
failed.append('%s: %s' % (comp_cls, comp_result.error))
if failed:
print 'Verification failed for the following components:'
print '\n'.join(failed)
else:
print 'Verification passed.'
@Command(
'write',
CmdArg('hwid', help='the encoded HWID string to write'))
def WriteHWIDWrapper(options):
"""Writes HWID to firmware GBB."""
hwid_utils.WriteHWID(options.hwid)
print 'HWID %r written to firmware GBB.' % options.hwid
@Command('read')
def ReadHWIDWrapper(options): # pylint: disable=unused-argument
"""Reads HWID from firmware GBB."""
print hwid_utils.GetHWIDString()
@Command(
'list-components',
CmdArg('comp_class', nargs='*', default=None,
help='the component classes to look up'))
def ListComponentsWrapper(options):
"""Lists components of the given class."""
components_list = hwid_utils.ListComponents(options.database,
options.comp_class)
print yaml.safe_dump(components_list, default_flow_style=False)
@Command(
'enumerate-hwid',
CmdArg('-i', '--image_id', default=None,
help='the image ID to enumerate.'),
CmdArg('-s', '--status', default='supported',
choices=['supported', 'released', 'all'],
help='the status of components to enumerate'))
def EnumerateHWIDWrapper(options):
"""Enumerates possible HWIDs."""
# Enumerating may take a very long time so we want to verbosely make logs.
logging.debug('Enumerating all HWIDs...')
hwids = hwid_utils.EnumerateHWID(options.database, options.image_id,
options.status)
logging.debug('Printing %d sorted HWIDs...', len(hwids))
for k, v in sorted(hwids.iteritems()):
print '%s: %s' % (k, v)
@Command('verify-database')
def VerifyHWIDDatabase(options):
"""Verifies the given HWID database."""
# Do nothing here since all the verifications are done when loading the
# database with HWID library.
print 'Database %s verified' % options.board
def ParseOptions(args=None):
"""Parse arguments and generate necessary options."""
return ParseCmdline('HWID command-line utilities', *_COMMON_ARGS,
args_to_parse=args)
def InitializeDefaultOptions(options):
if not options.hwid_db_path:
options.hwid_db_path = common.DEFAULT_HWID_DATA_PATH
if not options.board:
options.board = common.ProbeBoard()
# Build database doesn't need to initialize the database.
if options.command_name in ['build-database']:
return
board = build_board.BuildBoard(options.board).base
board_variant = build_board.BuildBoard(options.board).variant
# Use the variant specific HWID db if one exists, else reuse the one
# from the base board.
if board_variant and os.path.exists(
os.path.join(options.hwid_db_path, board_variant.upper())):
board = board_variant
options.board = board
# Create the Database object here since it's common to all functions.
logging.debug('Loading database file %s/%s...', options.hwid_db_path,
board.upper())
options.database = database.Database.LoadFile(
os.path.join(options.hwid_db_path, board.upper()),
verify_checksum=(not options.no_verify_checksum))
phase.OverridePhase(options.phase)
def Main():
"""Parses options, sets up logging, and runs the given subcommand."""
options = ParseOptions()
if options.verbose:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
InitializeDefaultOptions(options)
logging.debug('Perform command <%s>.. ', options.command_name)
options.command(options)
if __name__ == '__main__':
Main()