Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(25)

Side by Side Diff: chromeos_interface.py

Issue 6849042: Introduce crossystem wrapper in SAFT. (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/saft.git@master
Patch Set: Do not cache crossystem output. Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | runtests.sh » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
OLDNEW
« no previous file with comments | « no previous file | runtests.sh » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698