Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 '''A module to provide interface to ChromeOS services.''' | 6 '''A module to provide interface to ChromeOS services.''' |
| 7 | 7 |
| 8 import datetime | 8 import datetime |
| 9 import os | 9 import os |
| 10 import re | 10 import re |
| 11 import shutil | 11 import shutil |
| 12 import struct | 12 import struct |
| 13 import subprocess | 13 import subprocess |
| 14 import tempfile | 14 import tempfile |
| 15 import time | 15 import time |
| 16 | 16 |
| 17 # Source of ACPI information on ChromeOS machines. | |
| 18 ACPI_DIR = '/sys/devices/platform/chromeos_acpi' | |
| 19 | |
| 20 class ChromeOSInterfaceError(Exception): | 17 class ChromeOSInterfaceError(Exception): |
| 21 '''ChromeOS interface specific exception.''' | 18 '''ChromeOS interface specific exception.''' |
| 22 pass | 19 pass |
| 23 | 20 |
| 21 class Crossystem(object): | |
| 22 '''A wrapper for the crossystem utility.''' | |
| 23 | |
| 24 def init(self, cros_if): | |
| 25 self.cros_if = cros_if | |
| 26 | |
| 27 def __getattr__(self, name): | |
| 28 ''' | |
| 29 Retrieve a crosssystem attribute. | |
| 30 | |
| 31 Attempt to access crossystemobject.name will invoke `crossystem name' | |
| 32 and return the stdout as the value. | |
| 33 ''' | |
| 34 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
| |
| 35 'crossystem %s' % name)[0] | |
| 36 | |
| 37 def __setattr__(self, name, value): | |
| 38 if name in ('cros_if',): | |
| 39 self.__dict__[name] = value | |
| 40 else: | |
| 41 self.cros_if.run_shell_command('crossystem "%s=%s"' % (name, value)) | |
| 42 | |
| 43 | |
| 44 def get_boot_vector_base(self): | |
| 45 ''' | |
| 46 Convert system state into a legacy boot vector base. | |
| 47 | |
| 48 The first three legacy boot vector digits are the boot vector base | |
| 49 (the entire vector consists of 5 digits). They used to be reported by | |
| 50 the BIOS through ACPI, but that scheme has been superseded by the | |
| 51 'crossystem' interface. | |
| 52 | |
| 53 The digits of the boot vector base have the following significance | |
| 54 | |
| 55 - first digit - | |
| 56 1 - normal boot | |
| 57 2 - developer mode boot | |
| 58 3 - recovery initialed by pressing the recovery button | |
| 59 4 - recovery from developer mode warning screen | |
| 60 5 - recovery caused by both firmware images being invalid | |
| 61 6 - recovery caused by both kernel images being invalid | |
| 62 | |
| 63 - second digit - | |
| 64 0 - recovery firmware | |
| 65 1 - rewritable firmware A | |
| 66 2 - rewritable firmware B | |
| 67 | |
| 68 - third digit - | |
| 69 0 - Read only (recovery) EC firmware | |
| 70 1 - rewritable EC firmware | |
| 71 | |
| 72 | |
| 73 This function uses a three tuple of dictionaries to map current system | |
| 74 state as reported by 'crossystem' into the 'legacy' boot vector | |
| 75 digits. | |
| 76 | |
| 77 Each tuple element is a dictionary, where the key is the 'legacy' | |
| 78 representation of the state, and the value is a yet another dictionary | |
| 79 of name-value pairs. | |
| 80 | |
| 81 If all name-value pairs match those in the crossystem output, the | |
| 82 legacy representation number is returned as the appropriate vector | |
| 83 number. | |
| 84 | |
| 85 The function returns a list of three digits in symbolic representation. | |
| 86 | |
| 87 Should it be impossible to interpret the state, the function returns a | |
| 88 partially built list, which is an indication of a problem for the | |
| 89 caller (list shorter than 3 elements). | |
| 90 ''' | |
| 91 | |
| 92 vector_maps = ( | |
| 93 { # first vector position | |
| 94 '1': { | |
| 95 'devsw_boot': '0', | |
| 96 'recoverysw_boot': '0', | |
| 97 'recovery_reason' : '0', | |
| 98 'tried_fwb' : '0', | |
| 99 }, | |
| 100 }, | |
| 101 { # second vector position | |
| 102 '0': {'mainfw_type': 'recovery',}, | |
| 103 '1': { | |
| 104 'mainfw_type': 'normal', | |
| 105 'mainfw_act': 'A' | |
| 106 }, | |
| 107 '2': { | |
| 108 'mainfw_type': 'normal', | |
| 109 'mainfw_act': 'B' | |
| 110 }, | |
| 111 }, | |
| 112 { # third vector position | |
| 113 '0': {'ecfw_act': 'RO',}, | |
| 114 '1': {'ecfw_act': 'RW',}, | |
| 115 }, | |
| 116 ) | |
| 117 | |
| 118 boot_vector = [] | |
| 119 | |
| 120 for vector_map in vector_maps: | |
| 121 for (digit, values) in vector_map.iteritems(): | |
| 122 for (name, value) in values.iteritems(): | |
| 123 if self.__getattr__(name) != value: | |
| 124 break | |
| 125 else: | |
| 126 boot_vector.append(digit) | |
| 127 break | |
| 128 | |
| 129 return boot_vector | |
| 130 | |
| 131 def dump(self): | |
| 132 '''Dump all values as multiline text.''' | |
| 133 return '\n'.join(self.cros_if.run_shell_command_get_output( | |
| 134 'crossystem')) | |
| 135 | |
| 136 | |
| 24 class ChromeOSInterface(object): | 137 class ChromeOSInterface(object): |
| 25 '''An object to encapsulate OS services functions.''' | 138 '''An object to encapsulate OS services functions.''' |
| 26 | 139 |
| 27 def __init__(self, silent): | 140 def __init__(self, silent): |
| 28 '''Object construction time initialization. | 141 '''Object construction time initialization. |
| 29 | 142 |
| 30 The only parameter is the Boolean 'silent', when True the instance | 143 The only parameter is the Boolean 'silent', when True the instance |
| 31 does not duplicate log messages on the console. | 144 does not duplicate log messages on the console. |
| 32 ''' | 145 ''' |
| 146 | |
| 33 self.silent = silent | 147 self.silent = silent |
| 34 self.state_dir = None | 148 self.state_dir = None |
| 35 self.log_file = None | 149 self.log_file = None |
| 36 self.acpi_dir = ACPI_DIR | 150 self.cs = Crossystem() |
| 37 | 151 |
| 38 def init(self, state_dir=None, log_file=None): | 152 def init(self, state_dir=None, log_file=None): |
| 39 '''Initialize the ChromeOS interface object. | 153 '''Initialize the ChromeOS interface object. |
| 40 Args: | 154 Args: |
| 41 state_dir - a string, the name of the directory (as defined by the | 155 state_dir - a string, the name of the directory (as defined by the |
| 42 caller). The contents of this directory persist over | 156 caller). The contents of this directory persist over |
| 43 system restarts and power cycles. | 157 system restarts and power cycles. |
| 44 log_file - a string, the name of the log file kept in the state | 158 log_file - a string, the name of the log file kept in the state |
| 45 directory. | 159 directory. |
| 46 | 160 |
| 47 Default argument values support unit testing. | 161 Default argument values support unit testing. |
| 48 ''' | 162 ''' |
| 163 | |
| 164 self.cs.init(self) | |
| 49 self.state_dir = state_dir | 165 self.state_dir = state_dir |
| 50 | 166 |
| 51 if self.state_dir: | 167 if self.state_dir: |
| 52 if not os.path.exists(self.state_dir): | 168 if not os.path.exists(self.state_dir): |
| 53 try: | 169 try: |
| 54 os.mkdir(self.state_dir) | 170 os.mkdir(self.state_dir) |
| 55 except OSError, err: | 171 except OSError, err: |
| 56 raise ChromeOSInterfaceError(err) | 172 raise ChromeOSInterfaceError(err) |
| 57 if log_file: | 173 if log_file: |
| 58 self.log_file = os.path.join(state_dir, log_file) | 174 self.log_file = os.path.join(state_dir, log_file) |
| 59 | 175 |
| 60 def target_hosted(self): | 176 def target_hosted(self): |
| 61 '''Return True if running on a ChromeOS target.''' | 177 '''Return True if running on a ChromeOS target.''' |
| 62 signature = open('/proc/version_signature', 'r').read() | 178 signature = open('/proc/version_signature', 'r').read() |
| 63 return re.search(r'chrom(ium|e)os', signature, re.IGNORECASE) != None | 179 return re.search(r'chrom(ium|e)os', signature, re.IGNORECASE) != None |
| 64 | 180 |
| 65 def state_dir_file(self, file_name): | 181 def state_dir_file(self, file_name): |
| 66 '''Get a full path of a file in the state directory.''' | 182 '''Get a full path of a file in the state directory.''' |
| 67 return os.path.join(self.state_dir, file_name) | 183 return os.path.join(self.state_dir, file_name) |
| 68 | 184 |
| 69 def acpi_file(self, file_name): | |
| 70 '''Get a full path of a file in the ACPI directory.''' | |
| 71 return os.path.join(self.acpi_dir, file_name) | |
| 72 | |
| 73 def init_environment(self): | 185 def init_environment(self): |
| 74 '''Initialize Chrome OS interface environment. | 186 '''Initialize Chrome OS interface environment. |
| 75 | 187 |
| 76 If state dir was not set up by the constructor, create a temp | 188 If state dir was not set up by the constructor, create a temp |
| 77 directory, otherwise create the directory defined during construction | 189 directory, otherwise create the directory defined during construction |
| 78 of this object. | 190 of this object. |
| 79 | 191 |
| 80 Return the state directory name. | 192 Return the state directory name. |
| 81 ''' | 193 ''' |
| 82 | 194 |
| 83 if self.target_hosted() and not os.path.exists(self.acpi_dir): | |
| 84 raise ChromeOSInterfaceError( | |
| 85 'ACPI directory %s not found' % self.acpi_dir) | |
| 86 | |
| 87 if not self.state_dir: | 195 if not self.state_dir: |
| 88 self.state_dir = tempfile.mkdtemp(suffix='_saft') | 196 self.state_dir = tempfile.mkdtemp(suffix='_saft') |
| 89 else: | 197 else: |
| 90 # Wipe out state directory, to start the state machine clean. | 198 # Wipe out state directory, to start the state machine clean. |
| 91 shutil.rmtree(self.state_dir) | 199 shutil.rmtree(self.state_dir) |
| 92 # And recreate it | 200 # And recreate it |
| 93 self.init(self.state_dir, self.log_file) | 201 self.init(self.state_dir, self.log_file) |
| 94 | 202 |
| 95 return self.state_dir | 203 return self.state_dir |
| 96 | 204 |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 163 | 271 |
| 164 def is_removable_device(self, device): | 272 def is_removable_device(self, device): |
| 165 '''Check if a certain storage device is removable. | 273 '''Check if a certain storage device is removable. |
| 166 | 274 |
| 167 device - a string, file name of a storage device or a device partition | 275 device - a string, file name of a storage device or a device partition |
| 168 (as in /dev/sda[0-9]). | 276 (as in /dev/sda[0-9]). |
| 169 | 277 |
| 170 Returns True if the device is removable, False if not. | 278 Returns True if the device is removable, False if not. |
| 171 ''' | 279 ''' |
| 172 | 280 |
| 281 if not self.target_hosted(): | |
| 282 return False | |
| 283 | |
| 173 # Drop trailing digit(s) and letter(s) (if any) | 284 # Drop trailing digit(s) and letter(s) (if any) |
| 174 dev_name_stripper = re.compile('[0-9].*$') | 285 dev_name_stripper = re.compile('[0-9].*$') |
| 175 | 286 |
| 176 base_dev = dev_name_stripper.sub('', device.split('/')[2]) | 287 base_dev = dev_name_stripper.sub('', device.split('/')[2]) |
| 177 removable = int(open('/sys/block/%s/removable' % base_dev, 'r' | 288 removable = int(open('/sys/block/%s/removable' % base_dev, 'r' |
| 178 ).read()) | 289 ).read()) |
| 179 | 290 |
| 180 return removable == 1 | 291 return removable == 1 |
| 181 | 292 |
| 182 def get_root_dev(self): | 293 def get_root_dev(self): |
| 183 '''Return a string, the name of the root device''' | 294 '''Return a string, the name of the root device''' |
| 184 return self.run_shell_command_get_output('rootdev -s')[0] | 295 return self.run_shell_command_get_output('rootdev -s')[0] |
| 185 | 296 |
| 186 def run_shell_command_get_output(self, cmd): | 297 def run_shell_command_get_output(self, cmd): |
| 187 '''Run shell command and return its console output to the caller. | 298 '''Run shell command and return its console output to the caller. |
| 188 | 299 |
| 189 The output is returned as a list of strings stripped of the newline | 300 The output is returned as a list of strings stripped of the newline |
| 190 characters.''' | 301 characters.''' |
| 191 | 302 |
| 192 process = self.run_shell_command(cmd) | 303 process = self.run_shell_command(cmd) |
| 193 return [x.rstrip() for x in process.stdout.readlines()] | 304 return [x.rstrip() for x in process.stdout.readlines()] |
| 194 | 305 |
| 195 def boot_state_vector(self): | 306 def boot_state_vector(self): |
| 196 '''Read and return to caller a string describing the system state. | 307 '''Read and return to caller a string describing the system state. |
| 197 | 308 |
| 198 The string has a form of x:x:x:<removable>:<partition_number>, where | 309 The string has a form of x0:x1:x2:<removable>:<partition_number>, |
| 199 x' represent contents of the appropriate BINF files as reported by | 310 where the field meanings of X# are described in the |
| 200 ACPI, <removable> is set to 1 or 0 depending if the root device is | 311 Crossystem.get_boot_vector_base() docstring above. |
| 201 removable or not, and <partition number> is the last element of the | 312 |
| 202 root device name, designating the partition where the root fs is | 313 <removable> is set to 1 or 0 depending if the root device is removable |
| 203 mounted. | 314 or not, and <partition number> is the last element of the root device |
| 315 name, designating the partition where the root fs is mounted. | |
| 204 | 316 |
| 205 This vector fully describes the way the system came up. | 317 This vector fully describes the way the system came up. |
| 206 ''' | 318 ''' |
| 207 | 319 |
| 208 binf_fname_template = 'BINF.%d' | 320 state = self.cs.get_boot_vector_base() |
| 209 state = [] | 321 |
| 210 for index in range(3): | 322 if len(state) != 3: |
| 211 fname = os.path.join(self.acpi_dir, binf_fname_template % index) | 323 raise ChromeOSInterfaceError(self.cs.dump()) |
| 212 max_wait = 30 | 324 |
| 213 cycles = 0 | |
| 214 # In some cases (for instance when running off the flash file | |
| 215 # system) the ACPI files go not get created right away. Let's give | |
| 216 # it some time to settle. | |
| 217 while not os.path.exists(fname): | |
| 218 if cycles == max_wait: | |
| 219 self.log('%s is not present' % fname) | |
| 220 raise AssertionError | |
| 221 time.sleep(1) | |
| 222 cycles += 1 | |
| 223 if cycles: | |
| 224 self.log('ACPI took %d cycles' % cycles) | |
| 225 state.append(open(fname, 'r').read()) | |
| 226 root_dev = self.get_root_dev() | 325 root_dev = self.get_root_dev() |
| 227 state.append('%d' % int(self.is_removable_device(root_dev))) | 326 state.append('%d' % int(self.is_removable_device(root_dev))) |
| 228 state.append('%s' % root_dev[-1]) | 327 state.append('%s' % root_dev[-1]) |
| 229 state_str = ':'.join(state) | 328 state_str = ':'.join(state) |
| 230 return state_str | 329 return state_str |
| 231 | 330 |
| 232 def get_writeable_mount_point(self, dev, tmp_dir): | 331 def get_writeable_mount_point(self, dev, tmp_dir): |
| 233 '''Get mountpoint of the passed in device mounted in read/write mode. | 332 '''Get mountpoint of the passed in device mounted in read/write mode. |
| 234 | 333 |
| 235 If the device is already mounted and is writeable - return its mount | 334 If the device is already mounted and is writeable - return its mount |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 277 def read_partition(self, partition, size): | 376 def read_partition(self, partition, size): |
| 278 '''Read the requested partition, up to size bytes.''' | 377 '''Read the requested partition, up to size bytes.''' |
| 279 tmp_file = self.state_dir_file('part.tmp') | 378 tmp_file = self.state_dir_file('part.tmp') |
| 280 self.run_shell_command('dd if=%s of=%s bs=1 count=%d' % ( | 379 self.run_shell_command('dd if=%s of=%s bs=1 count=%d' % ( |
| 281 partition, tmp_file, size)) | 380 partition, tmp_file, size)) |
| 282 fileh = open(tmp_file, 'r') | 381 fileh = open(tmp_file, 'r') |
| 283 data = fileh.read() | 382 data = fileh.read() |
| 284 fileh.close() | 383 fileh.close() |
| 285 os.remove(tmp_file) | 384 os.remove(tmp_file) |
| 286 return data | 385 return data |
| OLD | NEW |