Chromium Code Reviews| Index: chromeos_interface.py |
| diff --git a/chromeos_interface.py b/chromeos_interface.py |
| index fe5b76e01ad38275b51804b5f548606b838e64c8..0df860969df5b950d49b0a2f61d8237cb262519c 100755 |
| --- a/chromeos_interface.py |
| +++ b/chromeos_interface.py |
| @@ -14,13 +14,126 @@ import subprocess |
| import tempfile |
| import time |
| -# Source of ACPI information on ChromeOS machines. |
| -ACPI_DIR = '/sys/devices/platform/chromeos_acpi' |
| - |
| class ChromeOSInterfaceError(Exception): |
| '''ChromeOS interface specific exception.''' |
| pass |
| +class Crossystem(object): |
| + '''A wrapper for the crossystem utility.''' |
| + |
| + def init(self, cros_if): |
| + self.cros_if = cros_if |
| + |
| + def __getattr__(self, name): |
| + ''' |
| + Retrieve a crosssystem attribute. |
| + |
| + Attempt to access crossystemobject.name will invoke `crossystem name' |
| + and return the stdout as the value. |
| + ''' |
| + return self.cros_if.run_shell_command_get_output( |
|
Randall Spangler
2011/04/18 20:42:54
What happens if crossystem exits non-zero (for exa
|
| + 'crossystem %s' % name)[0] |
| + |
| + def __setattr__(self, name, value): |
| + if name in ('cros_if',): |
| + self.__dict__[name] = value |
| + else: |
| + self.cros_if.run_shell_command('crossystem "%s=%s"' % (name, value)) |
| + |
| + |
| + def get_boot_vector_base(self): |
| + ''' |
| + Convert system state into a legacy boot vector base. |
| + |
| + The first three legacy boot vector digits are the boot vector base |
| + (the entire vector consists of 5 digits). They used to be reported by |
| + the BIOS through ACPI, but that scheme has been superseded by the |
| + 'crossystem' interface. |
| + |
| + The digits of the boot vector base have the following significance |
| + |
| + - first digit - |
| + 1 - normal boot |
| + 2 - developer mode boot |
| + 3 - recovery initialed by pressing the recovery button |
| + 4 - recovery from developer mode warning screen |
| + 5 - recovery caused by both firmware images being invalid |
| + 6 - recovery caused by both kernel images being invalid |
| + |
| + - second digit - |
| + 0 - recovery firmware |
| + 1 - rewritable firmware A |
| + 2 - rewritable firmware B |
| + |
| + - third digit - |
| + 0 - Read only (recovery) EC firmware |
| + 1 - rewritable EC firmware |
| + |
| + |
| + This function uses a three tuple of dictionaries to map current system |
| + state as reported by 'crossystem' into the 'legacy' boot vector |
| + digits. |
| + |
| + Each tuple element is a dictionary, where the key is the 'legacy' |
| + representation of the state, and the value is a yet another dictionary |
| + of name-value pairs. |
| + |
| + If all name-value pairs match those in the crossystem output, the |
| + legacy representation number is returned as the appropriate vector |
| + number. |
| + |
| + The function returns a list of three digits in symbolic representation. |
| + |
| + Should it be impossible to interpret the state, the function returns a |
| + partially built list, which is an indication of a problem for the |
| + caller (list shorter than 3 elements). |
| + ''' |
| + |
| + vector_maps = ( |
| + { # first vector position |
| + '1': { |
| + 'devsw_boot': '0', |
| + 'recoverysw_boot': '0', |
| + 'recovery_reason' : '0', |
| + 'tried_fwb' : '0', |
| + }, |
| + }, |
| + { # second vector position |
| + '0': {'mainfw_type': 'recovery',}, |
| + '1': { |
| + 'mainfw_type': 'normal', |
| + 'mainfw_act': 'A' |
| + }, |
| + '2': { |
| + 'mainfw_type': 'normal', |
| + 'mainfw_act': 'B' |
| + }, |
| + }, |
| + { # third vector position |
| + '0': {'ecfw_act': 'RO',}, |
| + '1': {'ecfw_act': 'RW',}, |
| + }, |
| + ) |
| + |
| + boot_vector = [] |
| + |
| + for vector_map in vector_maps: |
| + for (digit, values) in vector_map.iteritems(): |
| + for (name, value) in values.iteritems(): |
| + if self.__getattr__(name) != value: |
| + break |
| + else: |
| + boot_vector.append(digit) |
| + break |
| + |
| + return boot_vector |
| + |
| + def dump(self): |
| + '''Dump all values as multiline text.''' |
| + return '\n'.join(self.cros_if.run_shell_command_get_output( |
| + 'crossystem')) |
| + |
| + |
| class ChromeOSInterface(object): |
| '''An object to encapsulate OS services functions.''' |
| @@ -30,10 +143,11 @@ class ChromeOSInterface(object): |
| The only parameter is the Boolean 'silent', when True the instance |
| does not duplicate log messages on the console. |
| ''' |
| + |
| self.silent = silent |
| self.state_dir = None |
| self.log_file = None |
| - self.acpi_dir = ACPI_DIR |
| + self.cs = Crossystem() |
| def init(self, state_dir=None, log_file=None): |
| '''Initialize the ChromeOS interface object. |
| @@ -46,6 +160,8 @@ class ChromeOSInterface(object): |
| Default argument values support unit testing. |
| ''' |
| + |
| + self.cs.init(self) |
| self.state_dir = state_dir |
| if self.state_dir: |
| @@ -66,10 +182,6 @@ class ChromeOSInterface(object): |
| '''Get a full path of a file in the state directory.''' |
| return os.path.join(self.state_dir, file_name) |
| - def acpi_file(self, file_name): |
| - '''Get a full path of a file in the ACPI directory.''' |
| - return os.path.join(self.acpi_dir, file_name) |
| - |
| def init_environment(self): |
| '''Initialize Chrome OS interface environment. |
| @@ -80,10 +192,6 @@ class ChromeOSInterface(object): |
| Return the state directory name. |
| ''' |
| - if self.target_hosted() and not os.path.exists(self.acpi_dir): |
| - raise ChromeOSInterfaceError( |
| - 'ACPI directory %s not found' % self.acpi_dir) |
| - |
| if not self.state_dir: |
| self.state_dir = tempfile.mkdtemp(suffix='_saft') |
| else: |
| @@ -170,6 +278,9 @@ class ChromeOSInterface(object): |
| Returns True if the device is removable, False if not. |
| ''' |
| + if not self.target_hosted(): |
| + return False |
| + |
| # Drop trailing digit(s) and letter(s) (if any) |
| dev_name_stripper = re.compile('[0-9].*$') |
| @@ -195,34 +306,22 @@ class ChromeOSInterface(object): |
| def boot_state_vector(self): |
| '''Read and return to caller a string describing the system state. |
| - The string has a form of x:x:x:<removable>:<partition_number>, where |
| - x' represent contents of the appropriate BINF files as reported by |
| - ACPI, <removable> is set to 1 or 0 depending if the root device is |
| - removable or not, and <partition number> is the last element of the |
| - root device name, designating the partition where the root fs is |
| - mounted. |
| + The string has a form of x0:x1:x2:<removable>:<partition_number>, |
| + where the field meanings of X# are described in the |
| + Crossystem.get_boot_vector_base() docstring above. |
| + |
| + <removable> is set to 1 or 0 depending if the root device is removable |
| + or not, and <partition number> is the last element of the root device |
| + name, designating the partition where the root fs is mounted. |
| This vector fully describes the way the system came up. |
| ''' |
| - binf_fname_template = 'BINF.%d' |
| - state = [] |
| - for index in range(3): |
| - fname = os.path.join(self.acpi_dir, binf_fname_template % index) |
| - max_wait = 30 |
| - cycles = 0 |
| - # In some cases (for instance when running off the flash file |
| - # system) the ACPI files go not get created right away. Let's give |
| - # it some time to settle. |
| - while not os.path.exists(fname): |
| - if cycles == max_wait: |
| - self.log('%s is not present' % fname) |
| - raise AssertionError |
| - time.sleep(1) |
| - cycles += 1 |
| - if cycles: |
| - self.log('ACPI took %d cycles' % cycles) |
| - state.append(open(fname, 'r').read()) |
| + state = self.cs.get_boot_vector_base() |
| + |
| + if len(state) != 3: |
| + raise ChromeOSInterfaceError(self.cs.dump()) |
| + |
| root_dev = self.get_root_dev() |
| state.append('%d' % int(self.is_removable_device(root_dev))) |
| state.append('%s' % root_dev[-1]) |