blob: 50b7673bb0367ae9a54cd46fe848dd60f34b117a [file] [log] [blame]
Tammo Spalink9a96b8a2012-04-03 11:10:41 +08001#!/usr/bin/python
2#
3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Google Factory Tool.
8
9This tool is indended to be used on factory assembly lines. It
10provides all of the Google required test functionality and must be run
11on each device as part of the assembly process.
12"""
13
14
15import logging
16import os
17import re
18import sys
19
Tammo Spalink86a61c62012-05-25 15:10:35 +080020from tempfile import gettempdir
21
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080022import bmpblk
23import crosfw
24import hwid_tool
25import probe
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080026import report_upload
27import vpd_data
28
29from common import Error, ParseKeyValueData, SetupLogging, Shell, YamlWrite
30from hacked_argparse import CmdArg, Command, ParseCmdline, verbosity_cmd_arg
31from tempfile import NamedTemporaryFile
32
Tammo Spalink86a61c62012-05-25 15:10:35 +080033# TODO(tammo): Remove imp logic once the cros/factory code moves into this repo.
34import imp
35at_common = imp.find_module('common', ['/usr/local/autotest/client/bin'])
36imp.load_module('at_common', *at_common)
37from autotest_lib.client.cros.factory.event_log import EventLog, EVENT_LOG_DIR
38from autotest_lib.client.cros.factory.event_log import TimeString, TimedUuid
39from autotest_lib.client.cros.factory import FACTORY_LOG_PATH
40
41
42# Use a global event log, so that only a single log is created when
43# gooftool is called programmatically.
44_event_log = EventLog('gooftool')
45
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080046
47def GetPrimaryDevicePath(partition=None):
48 def IsFixed(dev):
49 sysfs_path = '/sys/block/%s/removable' % dev
50 return (os.path.exists(sysfs_path) and
51 open(sysfs_path).read().strip() == '0')
52 alpha_re = re.compile(r'^/dev/([a-zA-Z]+)[0-9]+$')
53 alnum_re = re.compile(r'^/dev/([a-zA-Z]+[0-9]+)p[0-9]+$')
cychiangde1dee22012-05-22 09:42:09 +080054 matched_alnum = False
55 dev_set = set()
56 for path in Shell('cgpt find -t rootfs').stdout.strip().split():
57 for dev in alpha_re.findall(path):
58 if IsFixed(dev):
59 dev_set.add(dev)
60 matched_alnum = False
61 for dev in alnum_re.findall(path):
62 if IsFixed(dev):
63 dev_set.add(dev)
64 matched_alnum = True
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080065 if len(dev_set) != 1:
66 raise Error('zero or multiple primary devs: %s' % dev_set)
67 dev_path = os.path.join('/dev', dev_set.pop())
68 if partition is None:
69 return dev_path
cychiangde1dee22012-05-22 09:42:09 +080070 fmt_str = '%sp%d' if matched_alnum else '%s%d'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080071 return fmt_str % (dev_path, partition)
72
73
74def GetReleaseRootPartitionPath():
75 return GetPrimaryDevicePath(5)
76
77
78def GetReleaseKernelPartitionPath():
79 return GetPrimaryDevicePath(4)
80
81
82def FindScript(script_name):
Jon Salzd24269c2012-05-29 17:22:59 +080083 script_path = os.path.join(os.path.dirname(os.path.dirname(
84 os.path.realpath(__file__))), 'sh', script_name)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080085 if not os.path.exists(script_path):
86 raise Error('Needed script %s does not exist.' % script_path)
87 return script_path
88
89
Tammo Spalink86a61c62012-05-25 15:10:35 +080090def ReadVpd(fw_image_file, kind):
91 raw_vpd_data = Shell('vpd -i %s -l -f %s' % (kind, fw_image_file)).stdout
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080092 return ParseKeyValueData('"(.*)"="(.*)"$', raw_vpd_data)
93
94
Tammo Spalink86a61c62012-05-25 15:10:35 +080095def ReadRoVpd(fw_image_file):
96 return ReadVpd(fw_image_file, 'RO_VPD')
97
98
99def ReadRwVpd(fw_image_file):
100 return ReadVpd(fw_image_file, 'RW_VPD')
101
102
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800103@Command('write_hwid',
104 CmdArg('hwid', metavar='HWID', help='HWID string'))
105def WriteHwid(options):
106 """Write specified HWID value into the system BB."""
107 logging.debug('writing hwid string %r', options.hwid)
108 main_fw = crosfw.LoadMainFirmware()
109 Shell('gbb_utility --set --hwid="%s" "%s"' %
110 (options.hwid, main_fw.GetFileName()))
111 main_fw.Write(sections=['GBB'])
Tammo Spalink86a61c62012-05-25 15:10:35 +0800112 _event_log.Log('write_hwid', hwid=options.hwid)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800113
114
115@Command('probe',
116 CmdArg('--comps', nargs='*',
117 help='List of keys from the component_db registry.'),
118 CmdArg('--no_vol', action='store_true',
119 help='Do not probe volatile data.'),
120 CmdArg('--no_ic', action='store_true',
121 help='Do not probe initial_config data.'))
122def RunProbe(options):
123 """Print yaml-formatted breakdown of probed device properties."""
124 probe_results = probe.Probe(target_comp_classes=options.comps,
125 probe_volatile=not options.no_vol,
126 probe_initial_config=not options.no_ic)
127 print YamlWrite(probe_results.__dict__)
128
129
130_hwdb_path_cmd_arg = CmdArg(
131 '--hwdb_path', metavar='PATH',
132 default=hwid_tool.DEFAULT_HWID_DATA_PATH,
133 help='Path to the HWID database.')
134
135
Tammo Spalink214caf42012-05-28 10:45:00 +0800136@Command('verify_components',
137 _hwdb_path_cmd_arg,
138 CmdArg('comp_white_list', nargs='*'))
139def VerifyComponents(options):
140 """Verify that probable components all match entries in the component_db.
141
142 Probe for each component class in the comp_white_list and verify
143 that a corresponding match exists in the component_db -- make sure
144 that these components are present, that they have been approved, but
145 do not check against any specific BOM/HWID configurations.
146 """
147 hwdb = hwid_tool.ReadDatastore(options.hwdb_path)
148 if not options.comp_white_list:
149 sys.exit('ERROR: no component white list specified; possible choices:\n %s'
150 % '\n '.join(sorted(hwdb.comp_db.registry)))
151 for comp_class in options.comp_white_list:
152 if comp_class not in hwdb.comp_db.registry:
153 sys.exit('ERROR: specified white list component class %r does not exist'
154 ' in the component DB.' % comp_class)
155 probe_results = probe.Probe(target_comp_classes=options.comp_white_list,
156 probe_volatile=False, probe_initial_config=False)
157 probe_val_map = hwid_tool.CalcCompDbProbeValMap(hwdb.comp_db)
158 errors = []
159 matches = []
160 for comp_class in sorted(options.comp_white_list):
161 probe_val = probe_results.found_components.get(comp_class, None)
162 if probe_val is not None:
163 comp_name = probe_val_map.get(probe_val, None)
164 if comp_name is not None:
165 matches.append(comp_name)
166 else:
167 errors.append('unsupported %r component found with probe result'
168 ' %r (no matching name in the component DB)' %
169 (comp_class, probe_val))
170 else:
171 errors.append('missing %r component' % comp_class)
172 if errors:
173 print '\n'.join(errors)
174 sys.exit('component verification FAILURE')
175 else:
176 print 'component verification SUCCESS'
177 print 'found components:\n %s' % '\n '.join(matches)
178
179
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800180@Command('verify_hwid',
181 _hwdb_path_cmd_arg)
182def VerifyHwid(options):
183 """Verify system HWID properties match probed device properties.
184
185 First probe components, volatile and initial_config parameters for
186 the DUT. Then use the available device data to produce a list of
187 candidate HWIDs. Then verify the HWID from the DUT is present in
188 that list. Then verify that the DUT initial config values match
189 those specified for its HWID. Finally, verify that VPD contains all
190 the necessary fields as specified by the board data, and when
191 possible verify that values are legitimate.
192 """
193 hwdb = hwid_tool.ReadDatastore(options.hwdb_path)
194 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
195 gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
196 hwid = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
197 hwid_properties = hwid_tool.LookupHwidProperties(hwdb, hwid)
198 logging.info('Verifying system HWID: %r', hwid_properties.hwid)
199 logging.debug('expected system properties:\n%s',
200 YamlWrite(hwid_properties.__dict__))
201 probe_results = probe.Probe()
202 cooked_results = hwid_tool.CookProbeResults(
203 hwdb, probe_results, hwid_properties.board)
204 logging.debug('found system properties:\n%s',
205 YamlWrite(cooked_results.__dict__))
Jon Salz51932222012-06-01 12:54:15 +0800206 _event_log.Log('probe',
207 results=cooked_results.__dict__)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800208 match_errors = []
209 for comp_class, expected_name in hwid_properties.component_map.items():
210 if expected_name == 'ANY':
211 continue
212 if expected_name == cooked_results.matched_components.get(comp_class, None):
213 continue
214 if comp_class in probe_results.missing_components:
215 match_errors.append(' %s component mismatch, expected %s, found nothing'
216 % (comp_class, expected_name))
217 else:
218 probe_value = probe_results.found_components.get(comp_class, None)
219 match_errors.append(' %s component mismatch, expected %s, found %r' %
220 (comp_class, expected_name, probe_value))
221 if match_errors:
222 raise Error('HWID verification FAILED.\n%s' % '\n'.join(match_errors))
223 if hwid_properties.volatile not in cooked_results.matched_volatile_tags:
224 msg = (' HWID specified volatile %s, but found match only for %s' %
225 (hwid_properties.volatile,
226 ', '.join(cooked_results.matched_volatile_tags)))
227 raise Error('HWID verification FAILED.\n%s' % msg)
228 if (hwid_properties.initial_config is not None and
229 hwid_properties.initial_config not in
230 cooked_results.matched_initial_config_tags):
231 msg = (' HWID specified initial_config %s, but only found match for [%s]' %
232 (hwid_properties.initial_config,
233 ', '.join(cooked_results.matched_initial_config_tags)))
234 raise Error('HWID verification FAILED.\n%s' % msg)
235 # TODO(tammo): Verify HWID status is supported (or deprecated for RMA).
236 ro_vpd = ReadRoVpd(main_fw_file)
237 for field in hwid_properties.vpd_ro_field_list:
238 if field not in ro_vpd:
239 raise Error('Missing required VPD field: %s' % field)
240 known_valid_values = vpd_data.KNOWN_VPD_FIELD_DATA.get(field, None)
241 value = ro_vpd[field]
242 if known_valid_values is not None and value not in known_valid_values:
243 raise Error('Invalid VPD entry : field %r, value %r' % (field, value))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800244 rw_vpd = ReadRwVpd(main_fw_file)
245 _event_log.Log(
246 'verify_hwid',
247 matched_components=cooked_results.matched_components,
248 initial_configs=cooked_results.matched_initial_config_tags,
249 volatiles=cooked_results.matched_volatile_tags,
250 ro_vpd=ro_vpd,
251 rw_vpd=rw_vpd)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800252
253
254@Command('verify_keys')
255def VerifyKeys(options):
256 """Verify keys in firmware and SSD match."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800257 script = FindScript('verify_keys.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800258 kernel_device = GetReleaseKernelPartitionPath()
259 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
260 result = Shell('%s %s %s' % (script, kernel_device, main_fw_file))
261 if not result.success:
262 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
263
264
265@Command('set_fw_bitmap_locale')
266def SetFirmwareBitmapLocale(options):
267 """Use VPD locale value to set firmware bitmap default language."""
268 image_file = crosfw.LoadMainFirmware().GetFileName()
269 locale = ReadRoVpd(image_file).get('initial_locale', None)
270 if locale is None:
271 raise Error, 'Missing initial_locale VPD.'
272 bitmap_locales = []
273 with NamedTemporaryFile() as f:
274 Shell('gbb_utility -g --bmpfv=%s %s' % (f.name, image_file))
275 bmpblk_data = bmpblk.unpack_bmpblock(f.read())
276 bitmap_locales = bmpblk_data.get('locales', bitmap_locales)
277 # Some locale values are just a language code and others are a
278 # hyphen-separated language code and country code pair. We care
279 # only about the language code part.
280 language_code = locale.partition('-')[0]
281 if language_code not in bitmap_locales:
282 raise Error, ('Firmware bitmaps do not contain support for the specified '
283 'initial locale language %r' % language_code)
284 else:
285 locale_index = bitmap_locales.index(language_code)
286 logging.info('Firmware bitmap initial locale set to %d (%s).',
287 locale_index, bitmap_locales[locale_index])
288 Shell('crossystem loc_idx=%d' % locale_index)
289
290
291@Command('verify_system_time')
292def VerifySystemTime(options):
293 """Verify system time is later than release filesystem creation time."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800294 script = FindScript('verify_system_time.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800295 rootfs_device = GetReleaseRootPartitionPath()
296 result = Shell('%s %s' % (script, rootfs_device))
297 if not result.success:
298 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
299
300
301@Command('verify_rootfs')
302def VerifyRootFs(options):
303 """Verify rootfs on SSD is valid by checking hash."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800304 script = FindScript('verify_rootfs.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800305 rootfs_device = GetReleaseRootPartitionPath()
306 result = Shell('%s %s' % (script, rootfs_device))
307 if not result.success:
308 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
309
310
311@Command('verify_switch_wp')
312def VerifyWpSwitch(options):
313 """Verify hardware write protection switch is enabled."""
314 if Shell('crossystem wpsw_cur').stdout.strip() != '1':
315 raise Error, 'write protection is disabled'
316
317
318@Command('verify_switch_dev')
319def VerifyDevSwitch(options):
320 """Verify developer switch is disabled."""
Hung-Te Lin8f643e32012-05-23 18:40:47 +0800321 result = Shell('crossystem devsw_cur')
322 if result.success:
323 if result.stdout.strip() != '0':
324 raise Error, 'developer mode is enabled'
325 else:
326 return
327 # Try ChromeOS-EC. This may hang 15 seconds if the EC does not respond.
328 logging.warn('VerifyDevSwitch: Trying ChromeOS-EC...')
329 if not Shell('ectool vboot 0').success:
330 raise Error, 'failed to turn off developer mode.'
331 # TODO(hungte) Verify if the switch is turned off properly, using "ectoo
332 # vboot" and parse the key-value pairs, when the names are determined.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800333
334
335@Command('write_protect')
336def EnableFwWp(options):
337 """Enable then verify firmware write protection."""
338
339 def WriteProtect(fw_file_path, fw_type, section):
340 """Calculate protection size, then invoke flashrom.
341
342 Our supported chips only allow write protecting half their total
343 size, so we parition the flash chipset space accordingly.
344 """
345 raw_image = open(fw_file_path, 'rb').read()
346 image = crosfw.FirmwareImage(raw_image)
347 if not image.has_section(section):
348 raise Error('could not find %s firmware section %s' % (fw_type, section))
349 section_data = image.get_section_area(section)
350 protectable_size = len(raw_image) / 2
351 ro_a = int(section_data[0] / protectable_size)
352 ro_b = int((section_data[0] + section_data[1] - 1) / protectable_size)
353 if ro_a != ro_b:
354 raise Error("%s firmware section %s has illegal size" %
355 (fw_type, section))
356 ro_offset = ro_a * protectable_size
357 logging.debug('write protecting %s', fw_type)
358 crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, protectable_size)
359
360 WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800361 _event_log.Log('wp', fw='main')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800362 ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
363 if ec_fw_file is not None:
364 WriteProtect(ec_fw_file, 'ec', 'EC_RO')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800365 _event_log.Log('wp', fw='ec')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800366 else:
367 logging.warning('EC not write protected (seems there is no EC flash).')
368
369
370@Command('clear_gbb_flags')
371def ClearGbbFlags(options):
372 """Zero out the GBB flags, in preparation for transition to release state.
373
374 No GBB flags are set in release/shipping state, but they are useful
375 for factory/development. See "gbb_utility --flags" for details.
376 """
Tammo Spalink461ddce2012-05-10 19:28:55 +0800377 script = FindScript('clear_gbb_flags.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800378 result = Shell(script)
379 if not result.success:
380 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800381 _event_log.Log('clear_gbb_flags')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800382
383
384@Command('prepare_wipe',
385 CmdArg('--fast', action='store_true',
386 help='use non-secure but faster wipe method.'))
387def PrepareWipe(options):
388 """Prepare system for transition to release state in next reboot."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800389 script = FindScript('prepare_wipe.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800390 tag = 'fast' if options.fast else ''
391 rootfs_device = GetReleaseRootPartitionPath()
392 result = Shell('FACTORY_WIPE_TAGS=%s %s %s' % (tag, script, rootfs_device))
393 if not result.success:
394 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
395
396
397@Command('verify',
398 CmdArg('--dev', action='store_true',
399 help='Do not verify switch state (dev mode and fw wp).'),
400 _hwdb_path_cmd_arg)
401def Verify(options):
402 """Verifies if whole factory process is ready for finalization.
403
404 This routine performs all the necessary checks to make sure the
405 device is ready to be finalized, but does not modify state. These
406 checks include dev switch, firmware write protection switch, hwid,
407 system time, keys, and root file system.
408 """
409 if not options.dev:
410 VerifyDevSwitch({})
411 VerifyWpSwitch({})
412 VerifyHwid(options)
413 VerifySystemTime({})
414 VerifyKeys({})
415 VerifyRootFs({})
416
417
Tammo Spalink86a61c62012-05-25 15:10:35 +0800418@Command('log_system_details')
419def LogSystemDetails(options):
420 """Write miscellaneous system details to the event log."""
421 raw_cs_data = Shell('crossystem').stdout.strip().splitlines()
422 # The crossytem output contains many lines like:
423 # 'key = value # description'
424 # Use regexps to pull out the key-value pairs and build a dict.
425 cs_data = dict((k, v.strip()) for k, v in
426 map(lambda x: re.findall(r'\A(\S+)\s+=\s+(.*)#.*\Z', x)[0],
427 raw_cs_data))
428 _event_log.Log(
429 'system_details',
430 platform_name=Shell('mosys platform name').stdout.strip(),
431 crossystem=cs_data,
432 modem_status=Shell('modem status').stdout.splitlines(),
433 ec_wp_status=Shell(
434 'flashrom -p internal:bus=lpc --get-size 2>/dev/null && '
435 'flashrom -p internal:bus=lpc --wp-status || '
436 'echo "EC is not available."').stdout,
437 bios_wp_status = Shell(
438 'flashrom -p internal:bus=spi --wp-status').stdout)
439
440
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800441_upload_method_cmd_arg = CmdArg(
442 '--upload_method', metavar='METHOD:PARAM',
443 help=('How to perform the upload. METHOD should be one of '
444 '{ftp, shopfloor, curl, cpfe, custom}.'))
445
446
447@Command('upload_report',
448 _upload_method_cmd_arg)
449def UploadReport(options):
450 """Create and a report containing key device details."""
Tammo Spalink86a61c62012-05-25 15:10:35 +0800451 ro_vpd = ReadRoVpd(crosfw.LoadMainFirmware().GetFileName())
452 device_sn = ro_vpd.get('serial_number', None)
453 if device_sn is None:
454 logging.warning('RO_VPD missing device serial number')
455 device_sn = 'MISSING_SN_' + TimedUuid()
456 target_name = '%s_%s.tbz2' % (TimeString(), device_sn)
457 target_path = os.path.join(gettempdir(), target_name)
458 # Intentionally ignoring dotfiles in EVENT_LOG_DIR.
459 tar_cmd = 'cd %s ; tar cjf %s *' % (EVENT_LOG_DIR, target_path)
460 tar_cmd += ' --add-file %s' % FACTORY_LOG_PATH
461 cmd_result = Shell(tar_cmd)
462 if not cmd_result.success:
463 raise Error('unable to tar event logs, cmd %r failed, stderr: %r' %
464 (tar_cmd, cmd_result.stderr))
465 if options.upload_method is None or options.upload_method == 'none':
466 logging.warning('REPORT UPLOAD SKIPPED (report left at %s)', target_path)
467 return
468 method, param = options.upload_method.split(':', 1)
469 if method == 'shopfloor':
470 report_upload.ShopFloorUpload(target_path, param)
471 elif method == 'ftp':
472 report_upload.FtpUpload(target_path, 'ftp://' + param)
473 elif method == 'ftps':
474 report_upload.CurlUrlUpload(target_path, '--ftp-ssl-reqd ftp:%s' % param)
475 elif method == 'cpfe':
476 report_upload.CpfeUpload(target_path, param)
477 else:
478 raise Error('unknown report upload method %r', method)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800479
480
481@Command('finalize',
482 CmdArg('--dev', action='store_true',
483 help='Do not verify or alter write protection or dev mode.'),
484 CmdArg('--fast', action='store_true',
485 help='use non-secure but faster wipe method.'),
486 _hwdb_path_cmd_arg,
487 _upload_method_cmd_arg)
488def Finalize(options):
489 """Verify system readiness and trigger transition into release state.
490
491 This routine first verifies system state (see verify command), then
492 clears all of the testing flags from the GBB, then modifies firmware
493 bitmaps to match locale. Then it enables firmware write protection
494 and sets the necessary boot flags to cause wipe of the factory image
495 on the next boot.
496 """
497 ClearGbbFlags({})
498 Verify(options)
499 SetFirmwareBitmapLocale({})
500 if not options.dev:
501 EnableFwWp({})
Jon Salza0f58e02012-05-29 19:33:39 +0800502 LogSystemDetails(options)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800503 UploadReport(options)
504 PrepareWipe(options)
505
506
507def Main():
508 """Run sub-command specified by the command line args."""
509 options = ParseCmdline(
510 'Perform Google required factory tests.',
511 CmdArg('-l', '--log', metavar='PATH',
512 help='Write logs to this file.'),
513 verbosity_cmd_arg
514 )
515 SetupLogging(options.verbosity, options.log)
516 logging.debug('gooftool options: %s', repr(options))
517 try:
518 logging.debug('GOOFTOOL command %r', options.command_name)
519 options.command(options)
520 logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
521 except Error, e:
522 logging.exception(e)
523 sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
524 except Exception, e:
525 logging.exception(e)
526 sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
527
528
529if __name__ == '__main__':
530 Main()