blob: 1450d712eb30b29c12f3b3e5b143d36f05729eb5 [file] [log] [blame]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001import os
2import re
3import time
4import logging
5import posixpath
6import subprocess
7import tempfile
8import threading
9from collections import namedtuple
10
11from devlib.host import LocalConnection, PACKAGE_BIN_DIRECTORY
12from devlib.module import get_module
13from devlib.platform import Platform
14from devlib.exception import TargetError, TargetNotRespondingError, TimeoutError
15from devlib.utils.ssh import SshConnection
16from devlib.utils.android import AdbConnection, AndroidProperties, adb_command, adb_disconnect
17from devlib.utils.misc import memoized, isiterable, convert_new_lines, merge_lists
18from devlib.utils.misc import ABI_MAP, get_cpu_name, ranges_to_list, escape_double_quotes
19from devlib.utils.types import integer, boolean, bitmask, identifier, caseless_string
20
21
Sebastian Goscik040daab2016-02-23 10:27:45 +000022FSTAB_ENTRY_REGEX = re.compile(r'(\S+) on (.+) type (\S+) \((\S+)\)')
Sebastian Goscik1890db72016-02-15 14:43:30 +000023ANDROID_SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn|Display Power: state)=([0-9]+|true|false|ON|OFF)',
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010024 re.IGNORECASE)
25ANDROID_SCREEN_RESOLUTION_REGEX = re.compile(r'mUnrestrictedScreen=\(\d+,\d+\)'
26 r'\s+(?P<width>\d+)x(?P<height>\d+)')
27DEFAULT_SHELL_PROMPT = re.compile(r'^.*(shell|root)@.*:/\S* [#$] ',
28 re.MULTILINE)
Patrick Bellasi9a8d5392017-02-17 15:28:07 +000029KVERSION_REGEX =re.compile(
30 r'(?P<version>\d+)(\.(?P<major>\d+)(\.(?P<minor>\d+)(-(rc)?(?P<rc>\d+))?)?)?(.*-g(?P<sha1>[0-9a-fA-F]{7,}))?'
31)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010032
33
34class Target(object):
35
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010036 path = None
37 os = None
38
39 default_modules = [
40 'hotplug',
41 'cpufreq',
42 'cpuidle',
43 'cgroups',
44 'hwmon',
45 ]
46
47 @property
48 def core_names(self):
49 return self.platform.core_names
50
51 @property
52 def core_clusters(self):
53 return self.platform.core_clusters
54
55 @property
56 def big_core(self):
57 return self.platform.big_core
58
59 @property
60 def little_core(self):
61 return self.platform.little_core
62
63 @property
64 def is_connected(self):
65 return self.conn is not None
66
67 @property
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010068 def connected_as_root(self):
Patrick Bellasi9ce57c02017-02-16 13:11:02 +000069 if self._connected_as_root is None:
70 result = self.execute('id')
71 self._connected_as_root = 'uid=0(' in result
72 return self._connected_as_root
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010073
74 @property
75 @memoized
76 def is_rooted(self):
77 if self.connected_as_root:
78 return True
79 try:
80 self.execute('ls /', timeout=2, as_root=True)
81 return True
82 except (TargetError, TimeoutError):
83 return False
84
85 @property
86 @memoized
Javi Merino16d87c62016-06-23 14:55:19 +010087 def needs_su(self):
88 return not self.connected_as_root and self.is_rooted
89
90 @property
91 @memoized
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010092 def kernel_version(self):
Sebastian Goscikbdbf4742016-02-24 14:26:18 +000093 return KernelVersion(self.execute('{} uname -r -v'.format(self.busybox)).strip())
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010094
95 @property
96 def os_version(self): # pylint: disable=no-self-use
97 return {}
98
99 @property
100 def abi(self): # pylint: disable=no-self-use
101 return None
102
103 @property
104 @memoized
105 def cpuinfo(self):
106 return Cpuinfo(self.execute('cat /proc/cpuinfo'))
107
108 @property
109 @memoized
110 def number_of_cpus(self):
111 num_cpus = 0
112 corere = re.compile(r'^\s*cpu\d+\s*$')
113 output = self.execute('ls /sys/devices/system/cpu')
114 for entry in output.split():
115 if corere.match(entry):
116 num_cpus += 1
117 return num_cpus
118
119 @property
120 @memoized
121 def config(self):
122 try:
123 return KernelConfig(self.execute('zcat /proc/config.gz'))
124 except TargetError:
125 for path in ['/boot/config', '/boot/config-$(uname -r)']:
126 try:
127 return KernelConfig(self.execute('cat {}'.format(path)))
128 except TargetError:
129 pass
130 return KernelConfig('')
131
132 @property
133 @memoized
134 def user(self):
135 return self.getenv('USER')
136
137 @property
138 def conn(self):
139 if self._connections:
140 tid = id(threading.current_thread())
141 if tid not in self._connections:
142 self._connections[tid] = self.get_connection()
143 return self._connections[tid]
144 else:
145 return None
146
147 def __init__(self,
148 connection_settings=None,
149 platform=None,
150 working_directory=None,
151 executables_directory=None,
152 connect=True,
153 modules=None,
154 load_default_modules=True,
155 shell_prompt=DEFAULT_SHELL_PROMPT,
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000156 conn_cls=None,
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100157 ):
Patrick Bellasi9ce57c02017-02-16 13:11:02 +0000158 self._connected_as_root = None
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100159 self.connection_settings = connection_settings or {}
Anouk Van Laer21f40032017-01-31 13:11:03 +0000160 # Set self.platform: either it's given directly (by platform argument)
161 # or it's given in the connection_settings argument
162 # If neither, create default Platform()
163 if platform is None:
164 self.platform = self.connection_settings.get('platform', Platform())
165 else:
166 self.platform = platform
167 # Check if the user hasn't given two different platforms
168 if 'platform' in self.connection_settings:
169 if connection_settings['platform'] is not platform:
170 raise TargetError('Platform specified in connection_settings '
171 '({}) differs from that directly passed '
172 '({})!)'
173 .format(connection_settings['platform'],
174 self.platform))
175 self.connection_settings['platform'] = self.platform
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100176 self.working_directory = working_directory
177 self.executables_directory = executables_directory
178 self.modules = modules or []
179 self.load_default_modules = load_default_modules
180 self.shell_prompt = shell_prompt
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000181 self.conn_cls = conn_cls
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100182 self.logger = logging.getLogger(self.__class__.__name__)
183 self._installed_binaries = {}
184 self._installed_modules = {}
185 self._cache = {}
186 self._connections = {}
187 self.busybox = None
188
189 if load_default_modules:
190 module_lists = [self.default_modules]
191 else:
192 module_lists = []
193 module_lists += [self.modules, self.platform.modules]
194 self.modules = merge_lists(*module_lists, duplicates='first')
195 self._update_modules('early')
196 if connect:
197 self.connect()
198
199 # connection and initialization
200
201 def connect(self, timeout=None):
202 self.platform.init_target_connection(self)
203 tid = id(threading.current_thread())
204 self._connections[tid] = self.get_connection(timeout=timeout)
Sergei Trofimov961f9572015-11-18 17:32:26 +0000205 self._resolve_paths()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100206 self.busybox = self.get_installed('busybox')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100207 self.platform.update_from_target(self)
Patrick Bellasib83e5182015-10-12 12:37:11 +0100208 self._update_modules('connected')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100209 if self.platform.big_core and self.load_default_modules:
210 self._install_module(get_module('bl'))
211
212 def disconnect(self):
213 for conn in self._connections.itervalues():
214 conn.close()
215 self._connections = {}
216
217 def get_connection(self, timeout=None):
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000218 if self.conn_cls == None:
219 raise ValueError('Connection class not specified on Target creation.')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100220 return self.conn_cls(timeout=timeout, **self.connection_settings) # pylint: disable=not-callable
221
222 def setup(self, executables=None):
223 self.execute('mkdir -p {}'.format(self.working_directory))
224 self.execute('mkdir -p {}'.format(self.executables_directory))
225 self.busybox = self.install(os.path.join(PACKAGE_BIN_DIRECTORY, self.abi, 'busybox'))
Patrick Bellasif2eac512015-11-27 16:35:57 +0000226
227 # Setup shutils script for the target
228 shutils_ifile = os.path.join(PACKAGE_BIN_DIRECTORY, 'scripts', 'shutils.in')
229 shutils_ofile = os.path.join(PACKAGE_BIN_DIRECTORY, 'scripts', 'shutils')
230 shell_path = '/bin/sh'
231 if self.os == 'android':
232 shell_path = '/system/bin/sh'
233 with open(shutils_ifile) as fh:
234 lines = fh.readlines()
235 with open(shutils_ofile, 'w') as ofile:
236 for line in lines:
237 line = line.replace("__DEVLIB_SHELL__", shell_path)
238 line = line.replace("__DEVLIB_BUSYBOX__", self.busybox)
239 ofile.write(line)
240 self.shutils = self.install(os.path.join(PACKAGE_BIN_DIRECTORY, 'scripts', 'shutils'))
241
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100242 for host_exe in (executables or []): # pylint: disable=superfluous-parens
243 self.install(host_exe)
244
Anouk Van Laer21f40032017-01-31 13:11:03 +0000245 # Check for platform dependent setup procedures
246 self.platform.setup(self)
247
Patrick Bellasic4e46b72016-05-13 18:15:51 +0100248 # Initialize modules which requires Buxybox (e.g. shutil dependent tasks)
249 self._update_modules('setup')
250
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100251 def reboot(self, hard=False, connect=True, timeout=180):
252 if hard:
253 if not self.has('hard_reset'):
254 raise TargetError('Hard reset not supported for this target.')
255 self.hard_reset() # pylint: disable=no-member
256 else:
257 if not self.is_connected:
258 message = 'Cannot reboot target becuase it is disconnected. ' +\
259 'Either connect() first, or specify hard=True ' +\
260 '(in which case, a hard_reset module must be installed)'
261 raise TargetError(message)
262 self.reset()
Sergei Trofimovebe3a8a2016-02-25 10:28:45 +0000263 # Wait a fixed delay before starting polling to give the target time to
264 # shut down, otherwise, might create the connection while it's still shutting
265 # down resulting in subsequenct connection failing.
266 self.logger.debug('Waiting for target to power down...')
267 reset_delay = 20
268 time.sleep(reset_delay)
269 timeout = max(timeout - reset_delay, 10)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100270 if self.has('boot'):
271 self.boot() # pylint: disable=no-member
272 if connect:
273 self.connect(timeout=timeout)
274
275 # file transfer
276
277 def push(self, source, dest, timeout=None):
278 return self.conn.push(source, dest, timeout=timeout)
279
280 def pull(self, source, dest, timeout=None):
281 return self.conn.pull(source, dest, timeout=timeout)
282
283 # execution
284
Patrick Bellasif2eac512015-11-27 16:35:57 +0000285 def _execute_util(self, command, timeout=None, check_exit_code=True, as_root=False):
286 command = '{} {}'.format(self.shutils, command)
287 return self.conn.execute(command, timeout, check_exit_code, as_root)
288
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100289 def execute(self, command, timeout=None, check_exit_code=True, as_root=False):
290 return self.conn.execute(command, timeout, check_exit_code, as_root)
291
292 def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
293 return self.conn.background(command, stdout, stderr, as_root)
294
295 def invoke(self, binary, args=None, in_directory=None, on_cpus=None,
296 as_root=False, timeout=30):
297 """
298 Executes the specified binary under the specified conditions.
299
300 :binary: binary to execute. Must be present and executable on the device.
301 :args: arguments to be passed to the binary. The can be either a list or
302 a string.
303 :in_directory: execute the binary in the specified directory. This must
304 be an absolute path.
305 :on_cpus: taskset the binary to these CPUs. This may be a single ``int`` (in which
306 case, it will be interpreted as the mask), a list of ``ints``, in which
307 case this will be interpreted as the list of cpus, or string, which
308 will be interpreted as a comma-separated list of cpu ranges, e.g.
309 ``"0,4-7"``.
310 :as_root: Specify whether the command should be run as root
311 :timeout: If the invocation does not terminate within this number of seconds,
312 a ``TimeoutError`` exception will be raised. Set to ``None`` if the
313 invocation should not timeout.
314
Brendan Jackman27f545f2016-11-15 16:58:57 +0000315 :returns: output of command.
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100316 """
317 command = binary
318 if args:
319 if isiterable(args):
320 args = ' '.join(args)
321 command = '{} {}'.format(command, args)
322 if on_cpus:
323 on_cpus = bitmask(on_cpus)
324 command = '{} taskset 0x{:x} {}'.format(self.busybox, on_cpus, command)
325 if in_directory:
326 command = 'cd {} && {}'.format(in_directory, command)
327 return self.execute(command, as_root=as_root, timeout=timeout)
328
329 def kick_off(self, command, as_root=False):
330 raise NotImplementedError()
331
332 # sysfs interaction
333
334 def read_value(self, path, kind=None):
Javi Merino16d87c62016-06-23 14:55:19 +0100335 output = self.execute('cat \'{}\''.format(path), as_root=self.needs_su).strip() # pylint: disable=E1103
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100336 if kind:
337 return kind(output)
338 else:
339 return output
340
341 def read_int(self, path):
342 return self.read_value(path, kind=integer)
343
344 def read_bool(self, path):
345 return self.read_value(path, kind=boolean)
346
347 def write_value(self, path, value, verify=True):
348 value = str(value)
349 self.execute('echo {} > \'{}\''.format(value, path), check_exit_code=False, as_root=True)
350 if verify:
351 output = self.read_value(path)
352 if not output == value:
353 message = 'Could not set the value of {} to "{}" (read "{}")'.format(path, value, output)
354 raise TargetError(message)
355
356 def reset(self):
357 try:
Javi Merino16d87c62016-06-23 14:55:19 +0100358 self.execute('reboot', as_root=self.needs_su, timeout=2)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100359 except (TargetError, TimeoutError, subprocess.CalledProcessError):
360 # on some targets "reboot" doesn't return gracefully
361 pass
362
363 def check_responsive(self):
364 try:
365 self.conn.execute('ls /', timeout=5)
366 except (TimeoutError, subprocess.CalledProcessError):
367 raise TargetNotRespondingError(self.conn.name)
368
369 # process management
370
371 def kill(self, pid, signal=None, as_root=False):
372 signal_string = '-s {}'.format(signal) if signal else ''
373 self.execute('kill {} {}'.format(signal_string, pid), as_root=as_root)
374
375 def killall(self, process_name, signal=None, as_root=False):
376 for pid in self.get_pids_of(process_name):
Sergei Trofimov6351a3b2017-01-30 11:14:36 +0000377 try:
378 self.kill(pid, signal=signal, as_root=as_root)
379 except TargetError:
380 pass
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100381
382 def get_pids_of(self, process_name):
383 raise NotImplementedError()
384
385 def ps(self, **kwargs):
386 raise NotImplementedError()
387
388 # files
389
390 def file_exists(self, filepath):
391 command = 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi'
Brendan Jackmanc1b51522016-11-23 13:44:00 +0000392 output = self.execute(command.format(filepath), as_root=self.is_rooted)
393 return boolean(output.strip())
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100394
Sebastian Goscik33603c62016-02-15 15:07:19 +0000395 def directory_exists(self, filepath):
396 output = self.execute('if [ -d \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath))
397 # output from ssh my contain part of the expression in the buffer,
398 # split out everything except the last word.
399 return boolean(output.split()[-1]) # pylint: disable=maybe-no-member
400
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100401 def list_file_systems(self):
402 output = self.execute('mount')
403 fstab = []
404 for line in output.split('\n'):
405 line = line.strip()
406 if not line:
407 continue
408 match = FSTAB_ENTRY_REGEX.search(line)
409 if match:
410 fstab.append(FstabEntry(match.group(1), match.group(2),
411 match.group(3), match.group(4),
412 None, None))
413 else: # assume pre-M Android
414 fstab.append(FstabEntry(*line.split()))
415 return fstab
416
417 def list_directory(self, path, as_root=False):
418 raise NotImplementedError()
419
420 def get_workpath(self, name):
421 return self.path.join(self.working_directory, name)
422
423 def tempfile(self, prefix='', suffix=''):
424 names = tempfile._get_candidate_names() # pylint: disable=W0212
425 for _ in xrange(tempfile.TMP_MAX):
426 name = names.next()
427 path = self.get_workpath(prefix + name + suffix)
428 if not self.file_exists(path):
429 return path
430 raise IOError('No usable temporary filename found')
431
432 def remove(self, path, as_root=False):
433 self.execute('rm -rf {}'.format(path), as_root=as_root)
434
435 # misc
436 def core_cpus(self, core):
437 return [i for i, c in enumerate(self.core_names) if c == core]
438
439 def list_online_cpus(self, core=None):
440 path = self.path.join('/sys/devices/system/cpu/online')
441 output = self.read_value(path)
442 all_online = ranges_to_list(output)
443 if core:
444 cpus = self.core_cpus(core)
445 if not cpus:
446 raise ValueError(core)
447 return [o for o in all_online if o in cpus]
448 else:
449 return all_online
450
451 def list_offline_cpus(self):
452 online = self.list_online_cpus()
453 return [c for c in xrange(self.number_of_cpus)
454 if c not in online]
455
456 def getenv(self, variable):
457 return self.execute('echo ${}'.format(variable)).rstrip('\r\n')
458
459 def capture_screen(self, filepath):
460 raise NotImplementedError()
461
462 def install(self, filepath, timeout=None, with_name=None):
463 raise NotImplementedError()
464
465 def uninstall(self, name):
466 raise NotImplementedError()
467
Sebastian Goscik84151f92016-02-15 15:09:27 +0000468 def get_installed(self, name, search_system_binaries=True):
469 # Check user installed binaries first
Sergei Trofimovb5324532015-11-18 18:07:47 +0000470 if self.file_exists(self.executables_directory):
471 if name in self.list_directory(self.executables_directory):
472 return self.path.join(self.executables_directory, name)
Sebastian Goscik84151f92016-02-15 15:09:27 +0000473 # Fall back to binaries in PATH
474 if search_system_binaries:
475 for path in self.getenv('PATH').split(self.path.pathsep):
476 try:
477 if name in self.list_directory(path):
478 return self.path.join(path, name)
479 except TargetError:
480 pass # directory does not exist or no executable premssions
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100481
482 which = get_installed
483
Sebastian Goscikbe8f9722016-02-15 15:11:38 +0000484 def install_if_needed(self, host_path, search_system_binaries=True):
485
486 binary_path = self.get_installed(os.path.split(host_path)[1],
487 search_system_binaries=search_system_binaries)
488 if not binary_path:
489 binary_path = self.install(host_path)
490 return binary_path
491
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100492 def is_installed(self, name):
493 return bool(self.get_installed(name))
494
495 def bin(self, name):
496 return self._installed_binaries.get(name, name)
497
498 def has(self, modname):
499 return hasattr(self, identifier(modname))
500
Sergei Trofimov5a81fe92016-01-27 16:34:26 +0000501 def lsmod(self):
502 lines = self.execute('lsmod').splitlines()
503 entries = []
504 for line in lines[1:]: # first line is the header
505 if not line.strip():
506 continue
507 parts = line.split()
508 name = parts[0]
509 size = int(parts[1])
510 use_count = int(parts[2])
511 if len(parts) > 3:
Sergei Trofimov10a80d22016-01-27 17:02:59 +0000512 used_by = ''.join(parts[3:]).split(',')
Sergei Trofimov5a81fe92016-01-27 16:34:26 +0000513 else:
514 used_by = []
515 entries.append(LsmodEntry(name, size, use_count, used_by))
516 return entries
517
Sergei Trofimov10a80d22016-01-27 17:02:59 +0000518 def insmod(self, path):
519 target_path = self.get_workpath(os.path.basename(path))
520 self.push(path, target_path)
521 self.execute('insmod {}'.format(target_path), as_root=True)
522
Sergei Trofimovbaaa67b2016-07-14 11:00:24 +0100523
524 def extract(self, path, dest=None):
525 """
526 Extact the specified on-target file. The extraction method to be used
527 (unzip, gunzip, bunzip2, or tar) will be based on the file's extension.
528 If ``dest`` is specified, it must be an existing directory on target;
529 the extracted contents will be placed there.
530
531 Note that, depending on the archive file format (and therfore the
532 extraction method used), the original archive file may or may not exist
533 after the extraction.
534
535 The return value is the path to the extracted contents. In case of
536 gunzip and bunzip2, this will be path to the extracted file; for tar
537 and uzip, this will be the directory with the extracted file(s)
538 (``dest`` if it was specified otherwise, the directory that cotained
539 the archive).
540
541 """
542 for ending in ['.tar.gz', '.tar.bz', '.tar.bz2',
543 '.tgz', '.tbz', '.tbz2']:
544 if path.endswith(ending):
545 return self._extract_archive(path, 'tar xf {} -C {}', dest)
546
547 ext = self.path.splitext(path)[1]
548 if ext in ['.bz', '.bz2']:
549 return self._extract_file(path, 'bunzip2 -f {}', dest)
550 elif ext == '.gz':
551 return self._extract_file(path, 'gunzip -f {}', dest)
552 elif ext == '.zip':
553 return self._extract_archive(path, 'unzip {} -d {}', dest)
554 else:
555 raise ValueError('Unknown compression format: {}'.format(ext))
556
557 # internal methods
558
559 def _extract_archive(self, path, cmd, dest=None):
560 cmd = '{} ' + cmd # busybox
561 if dest:
562 extracted = dest
563 else:
564 extracted = self.path.dirname(path)
565 cmdtext = cmd.format(self.busybox, path, extracted)
566 self.execute(cmdtext)
567 return extracted
568
569 def _extract_file(self, path, cmd, dest=None):
570 cmd = '{} ' + cmd # busybox
571 cmdtext = cmd.format(self.busybox, path)
572 self.execute(cmdtext)
573 extracted = self.path.splitext(path)[0]
574 if dest:
575 self.execute('mv -f {} {}'.format(extracted, dest))
576 if dest.endswith('/'):
577 extracted = self.path.join(dest, self.path.basename(extracted))
578 else:
579 extracted = dest
580 return extracted
581
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100582 def _update_modules(self, stage):
583 for mod in self.modules:
584 if isinstance(mod, dict):
585 mod, params = mod.items()[0]
586 else:
587 params = {}
588 mod = get_module(mod)
589 if not mod.stage == stage:
590 continue
591 if mod.probe(self):
592 self._install_module(mod, **params)
593 else:
Javi Merinod0c71fb2016-01-18 12:27:48 +0000594 msg = 'Module {} is not supported by the target'.format(mod.name)
595 if self.load_default_modules:
596 self.logger.debug(msg)
597 else:
598 self.logger.warning(msg)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100599
600 def _install_module(self, mod, **params):
601 if mod.name not in self._installed_modules:
602 self.logger.debug('Installing module {}'.format(mod.name))
603 mod.install(self, **params)
604 self._installed_modules[mod.name] = mod
605 else:
606 self.logger.debug('Module {} is already installed.'.format(mod.name))
607
Sergei Trofimov961f9572015-11-18 17:32:26 +0000608 def _resolve_paths(self):
609 raise NotImplementedError()
610
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100611
612class LinuxTarget(Target):
613
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100614 path = posixpath
615 os = 'linux'
616
617 @property
618 @memoized
619 def abi(self):
Sebastian Goscik5880f6e2016-06-16 13:31:53 +0100620 value = self.execute('uname -m').strip()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100621 for abi, architectures in ABI_MAP.iteritems():
622 if value in architectures:
623 result = abi
624 break
625 else:
626 result = value
627 return result
628
629 @property
630 @memoized
631 def os_version(self):
632 os_version = {}
633 try:
634 command = 'ls /etc/*-release /etc*-version /etc/*_release /etc/*_version 2>/dev/null'
635 version_files = self.execute(command, check_exit_code=False).strip().split()
636 for vf in version_files:
637 name = self.path.basename(vf)
638 output = self.read_value(vf)
639 os_version[name] = output.strip().replace('\n', ' ')
640 except TargetError:
641 raise
642 return os_version
643
Sebastian Goscikf5b7c822016-02-23 17:10:01 +0000644 @property
645 @memoized
646 # There is currently no better way to do this cross platform.
647 # ARM does not have dmidecode
648 def model(self):
649 if self.file_exists("/proc/device-tree/model"):
650 raw_model = self.execute("cat /proc/device-tree/model")
651 return '_'.join(raw_model.split()[:2])
652 return None
653
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000654 def __init__(self,
655 connection_settings=None,
656 platform=None,
657 working_directory=None,
658 executables_directory=None,
659 connect=True,
660 modules=None,
661 load_default_modules=True,
662 shell_prompt=DEFAULT_SHELL_PROMPT,
663 conn_cls=SshConnection,
664 ):
665 super(LinuxTarget, self).__init__(connection_settings=connection_settings,
666 platform=platform,
667 working_directory=working_directory,
668 executables_directory=executables_directory,
669 connect=connect,
670 modules=modules,
671 load_default_modules=load_default_modules,
672 shell_prompt=shell_prompt,
673 conn_cls=conn_cls)
674
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100675 def connect(self, timeout=None):
676 super(LinuxTarget, self).connect(timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100677
678 def kick_off(self, command, as_root=False):
679 command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
680 return self.conn.execute(command, as_root=as_root)
681
682 def get_pids_of(self, process_name):
683 """Returns a list of PIDs of all processes with the specified name."""
684 # result should be a column of PIDs with the first row as "PID" header
685 result = self.execute('ps -C {} -o pid'.format(process_name), # NOQA
686 check_exit_code=False).strip().split()
687 if len(result) >= 2: # at least one row besides the header
688 return map(int, result[1:])
689 else:
690 return []
691
692 def ps(self, **kwargs):
693 command = 'ps -eo user,pid,ppid,vsize,rss,wchan,pcpu,state,fname'
694 lines = iter(convert_new_lines(self.execute(command)).split('\n'))
695 lines.next() # header
696
697 result = []
698 for line in lines:
699 parts = re.split(r'\s+', line, maxsplit=8)
700 if parts and parts != ['']:
701 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
702
703 if not kwargs:
704 return result
705 else:
706 filtered_result = []
707 for entry in result:
708 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
709 filtered_result.append(entry)
710 return filtered_result
711
712 def list_directory(self, path, as_root=False):
713 contents = self.execute('ls -1 {}'.format(path), as_root=as_root)
714 return [x.strip() for x in contents.split('\n') if x.strip()]
715
716 def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221
717 destpath = self.path.join(self.executables_directory,
718 with_name and with_name or self.path.basename(filepath))
719 self.push(filepath, destpath)
720 self.execute('chmod a+x {}'.format(destpath), timeout=timeout)
721 self._installed_binaries[self.path.basename(destpath)] = destpath
722 return destpath
723
724 def uninstall(self, name):
725 path = self.path.join(self.executables_directory, name)
726 self.remove(path)
727
728 def capture_screen(self, filepath):
729 if not self.is_installed('scrot'):
730 self.logger.debug('Could not take screenshot as scrot is not installed.')
731 return
732 try:
733
734 tmpfile = self.tempfile()
735 self.execute('DISPLAY=:0.0 scrot {}'.format(tmpfile))
736 self.pull(tmpfile, filepath)
737 self.remove(tmpfile)
738 except TargetError as e:
739 if "Can't open X dispay." not in e.message:
740 raise e
741 message = e.message.split('OUTPUT:', 1)[1].strip() # pylint: disable=no-member
742 self.logger.debug('Could not take screenshot: {}'.format(message))
743
Sergei Trofimov961f9572015-11-18 17:32:26 +0000744 def _resolve_paths(self):
745 if self.working_directory is None:
746 if self.connected_as_root:
747 self.working_directory = '/root/devlib-target'
748 else:
749 self.working_directory = '/home/{}/devlib-target'.format(self.user)
750 if self.executables_directory is None:
751 self.executables_directory = self.path.join(self.working_directory, 'bin')
752
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100753
754class AndroidTarget(Target):
755
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100756 path = posixpath
757 os = 'android'
Chris Redpath2a4eafa2016-09-27 12:08:33 +0100758 ls_command = ''
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100759
760 @property
761 @memoized
762 def abi(self):
763 return self.getprop()['ro.product.cpu.abi'].split('-')[0]
764
765 @property
766 @memoized
767 def os_version(self):
768 os_version = {}
769 for k, v in self.getprop().iteritems():
770 if k.startswith('ro.build.version'):
771 part = k.split('.')[-1]
772 os_version[part] = v
773 return os_version
774
775 @property
776 def adb_name(self):
777 return self.conn.device
778
779 @property
Sebastian Goscikf5b7c822016-02-23 17:10:01 +0000780 @memoized
Sebastian Goscikcafeb812016-02-15 15:17:32 +0000781 def android_id(self):
782 """
783 Get the device's ANDROID_ID. Which is
784
785 "A 64-bit number (as a hex string) that is randomly generated when the user
786 first sets up the device and should remain constant for the lifetime of the
787 user's device."
788
789 .. note:: This will get reset on userdata erasure.
790
791 """
792 output = self.execute('content query --uri content://settings/secure --projection value --where "name=\'android_id\'"').strip()
793 return output.split('value=')[-1]
794
795 @property
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100796 @memoized
Sebastian Goscikf5b7c822016-02-23 17:10:01 +0000797 def model(self):
798 try:
799 return self.getprop(prop='ro.product.device')
800 except KeyError:
801 return None
802
803 @property
804 @memoized
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100805 def screen_resolution(self):
806 output = self.execute('dumpsys window')
807 match = ANDROID_SCREEN_RESOLUTION_REGEX.search(output)
808 if match:
809 return (int(match.group('width')),
810 int(match.group('height')))
811 else:
812 return (0, 0)
813
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000814 def __init__(self,
815 connection_settings=None,
816 platform=None,
817 working_directory=None,
818 executables_directory=None,
819 connect=True,
820 modules=None,
821 load_default_modules=True,
822 shell_prompt=DEFAULT_SHELL_PROMPT,
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000823 conn_cls=AdbConnection,
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000824 package_data_directory="/data/data",
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000825 ):
826 super(AndroidTarget, self).__init__(connection_settings=connection_settings,
827 platform=platform,
828 working_directory=working_directory,
829 executables_directory=executables_directory,
830 connect=connect,
831 modules=modules,
832 load_default_modules=load_default_modules,
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000833 shell_prompt=shell_prompt,
834 conn_cls=conn_cls)
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000835 self.package_data_directory = package_data_directory
836
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100837 def reset(self, fastboot=False): # pylint: disable=arguments-differ
838 try:
839 self.execute('reboot {}'.format(fastboot and 'fastboot' or ''),
Javi Merino16d87c62016-06-23 14:55:19 +0100840 as_root=self.needs_su, timeout=2)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100841 except (TargetError, TimeoutError, subprocess.CalledProcessError):
842 # on some targets "reboot" doesn't return gracefully
843 pass
844
845 def connect(self, timeout=10, check_boot_completed=True): # pylint: disable=arguments-differ
846 start = time.time()
847 device = self.connection_settings.get('device')
848 if device and ':' in device:
849 # ADB does not automatically remove a network device from it's
850 # devices list when the connection is broken by the remote, so the
851 # adb connection may have gone "stale", resulting in adb blocking
852 # indefinitely when making calls to the device. To avoid this,
853 # always disconnect first.
854 adb_disconnect(device)
855 super(AndroidTarget, self).connect(timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100856
857 if check_boot_completed:
858 boot_completed = boolean(self.getprop('sys.boot_completed'))
859 while not boot_completed and timeout >= time.time() - start:
860 time.sleep(5)
861 boot_completed = boolean(self.getprop('sys.boot_completed'))
862 if not boot_completed:
863 raise TargetError('Connected but Android did not fully boot.')
864
865 def setup(self, executables=None):
866 super(AndroidTarget, self).setup(executables)
867 self.execute('mkdir -p {}'.format(self._file_transfer_cache))
868
Sebastian Goscik9af32ec2016-05-27 16:26:30 +0100869 def kick_off(self, command, as_root=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100870 """
871 Like execute but closes adb session and returns immediately, leaving the command running on the
872 device (this is different from execute(background=True) which keeps adb connection open and returns
873 a subprocess object).
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100874 """
Sebastian Goscik9af32ec2016-05-27 16:26:30 +0100875 if as_root is None:
Javi Merino16d87c62016-06-23 14:55:19 +0100876 as_root = self.needs_su
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100877 try:
Sergei Trofimovca0b6e82016-08-30 14:25:11 +0100878 command = 'cd {} && {} nohup {} &'.format(self.working_directory, self.busybox, command)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100879 output = self.execute(command, timeout=1, as_root=as_root)
880 except TimeoutError:
881 pass
882 else:
883 raise ValueError('Background command exited before timeout; got "{}"'.format(output))
884
Chris Redpath2a4eafa2016-09-27 12:08:33 +0100885 def __setup_list_directory(self):
886 # In at least Linaro Android 16.09 (which was their first Android 7 release) and maybe
887 # AOSP 7.0 as well, the ls command was changed.
888 # Previous versions default to a single column listing, which is nice and easy to parse.
889 # Newer versions default to a multi-column listing, which is not, but it does support
890 # a '-1' option to get into single column mode. Older versions do not support this option
891 # so we try the new version, and if it fails we use the old version.
892 self.ls_command = 'ls -1'
893 try:
894 self.execute('ls -1 /', as_root=False)
895 except TargetError:
896 self.ls_command = 'ls'
897
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100898 def list_directory(self, path, as_root=False):
Chris Redpath2a4eafa2016-09-27 12:08:33 +0100899 if self.ls_command == '':
900 self.__setup_list_directory()
901 contents = self.execute('{} {}'.format(self.ls_command, path), as_root=as_root)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100902 return [x.strip() for x in contents.split('\n') if x.strip()]
903
904 def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221
905 ext = os.path.splitext(filepath)[1].lower()
906 if ext == '.apk':
907 return self.install_apk(filepath, timeout)
908 else:
909 return self.install_executable(filepath, with_name)
910
911 def uninstall(self, name):
912 if self.package_is_installed(name):
913 self.uninstall_package(name)
914 else:
915 self.uninstall_executable(name)
916
917 def get_pids_of(self, process_name):
918 result = self.execute('ps {}'.format(process_name[-15:]), check_exit_code=False).strip()
919 if result and 'not found' not in result:
920 return [int(x.split()[1]) for x in result.split('\n')[1:]]
921 else:
922 return []
923
924 def ps(self, **kwargs):
925 lines = iter(convert_new_lines(self.execute('ps')).split('\n'))
926 lines.next() # header
927 result = []
928 for line in lines:
929 parts = line.split()
930 if parts:
931 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
932 if not kwargs:
933 return result
934 else:
935 filtered_result = []
936 for entry in result:
937 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
938 filtered_result.append(entry)
939 return filtered_result
940
941 def capture_screen(self, filepath):
942 on_device_file = self.path.join(self.working_directory, 'screen_capture.png')
943 self.execute('screencap -p {}'.format(on_device_file))
944 self.pull(on_device_file, filepath)
945 self.remove(on_device_file)
946
947 def push(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ
948 if not as_root:
949 self.conn.push(source, dest, timeout=timeout)
950 else:
951 device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep))
Sebastian Goscik1424ceb2016-02-15 15:27:19 +0000952 self.execute("mkdir -p '{}'".format(self.path.dirname(device_tempfile)))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100953 self.conn.push(source, device_tempfile, timeout=timeout)
Sebastian Goscik1424ceb2016-02-15 15:27:19 +0000954 self.execute("cp '{}' '{}'".format(device_tempfile, dest), as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100955
956 def pull(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ
957 if not as_root:
958 self.conn.pull(source, dest, timeout=timeout)
959 else:
960 device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep))
Sebastian Goscik1424ceb2016-02-15 15:27:19 +0000961 self.execute("mkdir -p '{}'".format(self.path.dirname(device_tempfile)))
962 self.execute("cp '{}' '{}'".format(source, device_tempfile), as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100963 self.conn.pull(device_tempfile, dest, timeout=timeout)
964
965 # Android-specific
966
Sebastian Goscik880a0bc2016-02-15 15:19:47 +0000967 def swipe_to_unlock(self, direction="horizontal"):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100968 width, height = self.screen_resolution
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100969 command = 'input swipe {} {} {} {}'
Sebastian Goscik880a0bc2016-02-15 15:19:47 +0000970 if direction == "horizontal":
971 swipe_heigh = height * 2 // 3
972 start = 100
973 stop = width - start
974 self.execute(command.format(start, swipe_heigh, stop, swipe_heigh))
975 if direction == "vertical":
976 swipe_middle = height / 2
977 swipe_heigh = height * 2 // 3
978 self.execute(command.format(swipe_middle, swipe_heigh, swipe_middle, 0))
979 else:
980 raise DeviceError("Invalid swipe direction: {}".format(self.swipe_to_unlock))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100981
982 def getprop(self, prop=None):
983 props = AndroidProperties(self.execute('getprop'))
984 if prop:
985 return props[prop]
986 return props
987
988 def is_installed(self, name):
989 return super(AndroidTarget, self).is_installed(name) or self.package_is_installed(name)
990
991 def package_is_installed(self, package_name):
992 return package_name in self.list_packages()
993
994 def list_packages(self):
995 output = self.execute('pm list packages')
996 output = output.replace('package:', '')
997 return output.split()
998
999 def get_package_version(self, package):
1000 output = self.execute('dumpsys package {}'.format(package))
1001 for line in convert_new_lines(output).split('\n'):
1002 if 'versionName' in line:
1003 return line.split('=', 1)[1]
1004 return None
1005
1006 def install_apk(self, filepath, timeout=None): # pylint: disable=W0221
1007 ext = os.path.splitext(filepath)[1].lower()
1008 if ext == '.apk':
Sebastian Goscik1424ceb2016-02-15 15:27:19 +00001009 return adb_command(self.adb_name, "install '{}'".format(filepath), timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001010 else:
1011 raise TargetError('Can\'t install {}: unsupported format.'.format(filepath))
1012
1013 def install_executable(self, filepath, with_name=None):
1014 self._ensure_executables_directory_is_writable()
1015 executable_name = with_name or os.path.basename(filepath)
1016 on_device_file = self.path.join(self.working_directory, executable_name)
1017 on_device_executable = self.path.join(self.executables_directory, executable_name)
1018 self.push(filepath, on_device_file)
1019 if on_device_file != on_device_executable:
Javi Merino16d87c62016-06-23 14:55:19 +01001020 self.execute('cp {} {}'.format(on_device_file, on_device_executable), as_root=self.needs_su)
1021 self.remove(on_device_file, as_root=self.needs_su)
1022 self.execute("chmod 0777 '{}'".format(on_device_executable), as_root=self.needs_su)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001023 self._installed_binaries[executable_name] = on_device_executable
1024 return on_device_executable
1025
1026 def uninstall_package(self, package):
1027 adb_command(self.adb_name, "uninstall {}".format(package), timeout=30)
1028
1029 def uninstall_executable(self, executable_name):
1030 on_device_executable = self.path.join(self.executables_directory, executable_name)
1031 self._ensure_executables_directory_is_writable()
Javi Merino16d87c62016-06-23 14:55:19 +01001032 self.remove(on_device_executable, as_root=self.needs_su)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001033
1034 def dump_logcat(self, filepath, filter=None, append=False, timeout=30): # pylint: disable=redefined-builtin
Sebastian Goscikaab487c2016-02-15 15:21:40 +00001035 op = '>>' if append else '>'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001036 filtstr = ' -s {}'.format(filter) if filter else ''
1037 command = 'logcat -d{} {} {}'.format(filtstr, op, filepath)
1038 adb_command(self.adb_name, command, timeout=timeout)
1039
1040 def clear_logcat(self):
1041 adb_command(self.adb_name, 'logcat -c', timeout=30)
1042
Patrick Bellasi9ce57c02017-02-16 13:11:02 +00001043 def adb_reboot_bootloader(self, timeout=30):
1044 adb_command(self.adb_name, 'reboot-bootloader', timeout)
1045
1046 def adb_root(self, enable=True):
1047 if enable:
1048 if self._connected_as_root:
1049 return
1050 adb_command(self.adb_name, 'root', timeout=30)
1051 self._connected_as_root = True
1052 return
1053 adb_command(self.adb_name, 'unroot', timeout=30)
1054 self._connected_as_root = False
1055
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001056 def is_screen_on(self):
1057 output = self.execute('dumpsys power')
1058 match = ANDROID_SCREEN_STATE_REGEX.search(output)
1059 if match:
1060 return boolean(match.group(1))
1061 else:
1062 raise TargetError('Could not establish screen state.')
1063
1064 def ensure_screen_is_on(self):
1065 if not self.is_screen_on():
1066 self.execute('input keyevent 26')
1067
Sergei Trofimov961f9572015-11-18 17:32:26 +00001068 def _resolve_paths(self):
1069 if self.working_directory is None:
1070 self.working_directory = '/data/local/tmp/devlib-target'
1071 self._file_transfer_cache = self.path.join(self.working_directory, '.file-cache')
1072 if self.executables_directory is None:
Sebastian Goscikff8261e2016-02-15 15:28:20 +00001073 self.executables_directory = '/data/local/tmp/bin'
Sergei Trofimov961f9572015-11-18 17:32:26 +00001074
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001075 def _ensure_executables_directory_is_writable(self):
1076 matched = []
1077 for entry in self.list_file_systems():
1078 if self.executables_directory.rstrip('/').startswith(entry.mount_point):
1079 matched.append(entry)
1080 if matched:
1081 entry = sorted(matched, key=lambda x: len(x.mount_point))[-1]
1082 if 'rw' not in entry.options:
1083 self.execute('mount -o rw,remount {} {}'.format(entry.device,
1084 entry.mount_point),
1085 as_root=True)
1086 else:
1087 message = 'Could not find mount point for executables directory {}'
1088 raise TargetError(message.format(self.executables_directory))
1089
Brendan Jackmanbaa32ec2017-02-09 11:53:11 +00001090 _charging_enabled_path = '/sys/class/power_supply/battery/charging_enabled'
1091
1092 @property
1093 def charging_enabled(self):
1094 """
1095 Whether drawing power to charge the battery is enabled
1096
1097 Not all devices have the ability to enable/disable battery charging
1098 (e.g. because they don't have a battery). In that case,
1099 ``charging_enabled`` is None.
1100 """
1101 if not self.file_exists(self._charging_enabled_path):
1102 return None
1103 return self.read_bool(self._charging_enabled_path)
1104
1105 @charging_enabled.setter
1106 def charging_enabled(self, enabled):
1107 """
1108 Enable/disable drawing power to charge the battery
1109
1110 Not all devices have this facility. In that case, do nothing.
1111 """
1112 if not self.file_exists(self._charging_enabled_path):
1113 return
1114 self.write_value(self._charging_enabled_path, int(bool(enabled)))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001115
1116FstabEntry = namedtuple('FstabEntry', ['device', 'mount_point', 'fs_type', 'options', 'dump_freq', 'pass_num'])
1117PsEntry = namedtuple('PsEntry', 'user pid ppid vsize rss wchan pc state name')
Sergei Trofimov5a81fe92016-01-27 16:34:26 +00001118LsmodEntry = namedtuple('LsmodEntry', ['name', 'size', 'use_count', 'used_by'])
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001119
1120
1121class Cpuinfo(object):
1122
1123 @property
1124 @memoized
1125 def architecture(self):
1126 for section in self.sections:
1127 if 'CPU architecture' in section:
1128 return section['CPU architecture']
1129 if 'architecture' in section:
1130 return section['architecture']
1131
1132 @property
1133 @memoized
1134 def cpu_names(self):
1135 cpu_names = []
1136 global_name = None
1137 for section in self.sections:
1138 if 'processor' in section:
1139 if 'CPU part' in section:
1140 cpu_names.append(_get_part_name(section))
1141 elif 'model name' in section:
1142 cpu_names.append(_get_model_name(section))
1143 else:
1144 cpu_names.append(None)
1145 elif 'CPU part' in section:
1146 global_name = _get_part_name(section)
1147 return [caseless_string(c or global_name) for c in cpu_names]
1148
1149 def __init__(self, text):
1150 self.sections = None
1151 self.text = None
1152 self.parse(text)
1153
1154 @memoized
1155 def get_cpu_features(self, cpuid=0):
1156 global_features = []
1157 for section in self.sections:
1158 if 'processor' in section:
1159 if int(section.get('processor')) != cpuid:
1160 continue
1161 if 'Features' in section:
1162 return section.get('Features').split()
Sergei Trofimov59f4f812015-12-15 18:07:34 +00001163 elif 'flags' in section:
1164 return section.get('flags').split()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001165 elif 'Features' in section:
1166 global_features = section.get('Features').split()
Sergei Trofimov59f4f812015-12-15 18:07:34 +00001167 elif 'flags' in section:
1168 global_features = section.get('flags').split()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001169 return global_features
1170
1171 def parse(self, text):
1172 self.sections = []
1173 current_section = {}
1174 self.text = text.strip()
1175 for line in self.text.split('\n'):
1176 line = line.strip()
1177 if line:
1178 key, value = line.split(':', 1)
1179 current_section[key.strip()] = value.strip()
1180 else: # not line
1181 self.sections.append(current_section)
1182 current_section = {}
1183 self.sections.append(current_section)
1184
1185 def __str__(self):
1186 return 'CpuInfo({})'.format(self.cpu_names)
1187
1188 __repr__ = __str__
1189
1190
1191class KernelVersion(object):
Brendan Jackman54adf802017-02-20 17:57:51 +00001192 """
1193 Class representing the version of a target kernel
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001194
Brendan Jackman54adf802017-02-20 17:57:51 +00001195 Not expected to work for very old (pre-3.0) kernel version numbers.
1196
1197 :ivar release: Version number/revision string. Typical output of
1198 ``uname -r``
1199 :type release: str
1200 :ivar version: Extra version info (aside from ``release``) reported by
1201 ``uname``
1202 :type version: str
1203 :ivar version_number: Main version number (e.g. 3 for Linux 3.18)
1204 :type version_number: int
1205 :ivar major: Major version number (e.g. 18 for Linux 3.18)
1206 :type major: int
1207 :ivar minor: Minor version number for stable kernels (e.g. 9 for 4.9.9). May
1208 be None
1209 :type minor: int
1210 :ivar rc: Release candidate number (e.g. 3 for Linux 4.9-rc3). May be None.
1211 :type rc: int
1212 :ivar sha1: Kernel git revision hash, if available (otherwise None)
1213 :type sha1: str
Brendan Jackman18b77b82017-02-20 17:52:56 +00001214
1215 :ivar parts: Tuple of version number components. Can be used for
1216 lexicographically comparing kernel versions.
1217 :type parts: tuple(int)
Brendan Jackman54adf802017-02-20 17:57:51 +00001218 """
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001219 def __init__(self, version_string):
1220 if ' #' in version_string:
1221 release, version = version_string.split(' #')
1222 self.release = release
1223 self.version = version
1224 elif version_string.startswith('#'):
1225 self.release = ''
1226 self.version = version_string
1227 else:
1228 self.release = version_string
1229 self.version = ''
1230
Patrick Bellasi9a8d5392017-02-17 15:28:07 +00001231 self.version_number = None
1232 self.major = None
1233 self.minor = None
1234 self.sha1 = None
1235 self.rc = None
1236 match = KVERSION_REGEX.match(version_string)
1237 if match:
Brendan Jackman03561ee2017-02-20 17:51:17 +00001238 groups = match.groupdict()
1239 self.version_number = int(groups['version'])
1240 self.major = int(groups['major'])
1241 if groups['minor'] is not None:
1242 self.minor = int(groups['minor'])
1243 if groups['rc'] is not None:
1244 self.rc = int(groups['rc'])
1245 if groups['sha1'] is not None:
1246 self.sha1 = match.group('sha1')
Patrick Bellasi9a8d5392017-02-17 15:28:07 +00001247
Brendan Jackman18b77b82017-02-20 17:52:56 +00001248 self.parts = (self.version_number, self.major, self.minor)
1249
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001250 def __str__(self):
1251 return '{} {}'.format(self.release, self.version)
1252
1253 __repr__ = __str__
1254
1255
1256class KernelConfig(object):
1257
1258 not_set_regex = re.compile(r'# (\S+) is not set')
1259
1260 @staticmethod
1261 def get_config_name(name):
1262 name = name.upper()
1263 if not name.startswith('CONFIG_'):
1264 name = 'CONFIG_' + name
1265 return name
1266
1267 def iteritems(self):
1268 return self._config.iteritems()
1269
1270 def __init__(self, text):
1271 self.text = text
1272 self._config = {}
1273 for line in text.split('\n'):
1274 line = line.strip()
1275 if line.startswith('#'):
1276 match = self.not_set_regex.search(line)
1277 if match:
1278 self._config[match.group(1)] = 'n'
1279 elif '=' in line:
1280 name, value = line.split('=', 1)
1281 self._config[name.strip()] = value.strip()
1282
1283 def get(self, name):
1284 return self._config.get(self.get_config_name(name))
1285
1286 def like(self, name):
1287 regex = re.compile(name, re.I)
1288 result = {}
1289 for k, v in self._config.iteritems():
1290 if regex.search(k):
1291 result[k] = v
1292 return result
1293
1294 def is_enabled(self, name):
1295 return self.get(name) == 'y'
1296
1297 def is_module(self, name):
1298 return self.get(name) == 'm'
1299
1300 def is_not_set(self, name):
1301 return self.get(name) == 'n'
1302
1303 def has(self, name):
1304 return self.get(name) in ['m', 'y']
1305
1306
1307class LocalLinuxTarget(LinuxTarget):
1308
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +00001309 def __init__(self,
1310 connection_settings=None,
1311 platform=None,
1312 working_directory=None,
1313 executables_directory=None,
1314 connect=True,
1315 modules=None,
1316 load_default_modules=True,
1317 shell_prompt=DEFAULT_SHELL_PROMPT,
1318 conn_cls=LocalConnection,
1319 ):
1320 super(LocalLinuxTarget, self).__init__(connection_settings=connection_settings,
1321 platform=platform,
1322 working_directory=working_directory,
1323 executables_directory=executables_directory,
1324 connect=connect,
1325 modules=modules,
1326 load_default_modules=load_default_modules,
1327 shell_prompt=shell_prompt,
1328 conn_cls=conn_cls)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001329
Sergei Trofimov961f9572015-11-18 17:32:26 +00001330 def _resolve_paths(self):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001331 if self.working_directory is None:
1332 self.working_directory = '/tmp'
1333 if self.executables_directory is None:
1334 self.executables_directory = '/tmp'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001335
1336
1337def _get_model_name(section):
1338 name_string = section['model name']
1339 parts = name_string.split('@')[0].strip().split()
1340 return ' '.join([p for p in parts
1341 if '(' not in p and p != 'CPU'])
1342
1343
1344def _get_part_name(section):
1345 implementer = section.get('CPU implementer', '0x0')
1346 part = section['CPU part']
1347 variant = section.get('CPU variant', '0x0')
1348 name = get_cpu_name(*map(integer, [implementer, part, variant]))
1349 if name is None:
1350 name = '{}/{}/{}'.format(implementer, part, variant)
1351 return name