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

Side by Side Diff: mojo/devtools/common/devtoolslib/android_shell.py

Issue 1437383002: Never try to use root to run the mojo tools. (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 5 years, 1 month 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
« no previous file with comments | « no previous file | mojo/devtools/common/devtoolslib/shell_arguments.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import atexit 5 import atexit
6 import hashlib 6 import hashlib
7 import logging 7 import logging
8 import os 8 import os
9 import os.path 9 import os.path
10 import random 10 import random
(...skipping 12 matching lines...) Expand all
23 23
24 # Tags used by mojo shell Java logging. 24 # Tags used by mojo shell Java logging.
25 _LOGCAT_JAVA_TAGS = [ 25 _LOGCAT_JAVA_TAGS = [
26 'AndroidHandler', 26 'AndroidHandler',
27 'MojoFileHelper', 27 'MojoFileHelper',
28 'MojoMain', 28 'MojoMain',
29 'MojoShellActivity', 29 'MojoShellActivity',
30 'MojoShellApplication', 30 'MojoShellApplication',
31 ] 31 ]
32 32
33 # Tags used by native logging reflected in the logcat.
34 _LOGCAT_NATIVE_TAGS = [
35 'chromium',
36 'sky',
37 ]
38
39 _MOJO_SHELL_PACKAGE_NAME = 'org.chromium.mojo.shell' 33 _MOJO_SHELL_PACKAGE_NAME = 'org.chromium.mojo.shell'
40 34
41 # Used to parse the output of `adb devices`. 35 # Used to parse the output of `adb devices`.
42 _ADB_DEVICES_HEADER = 'List of devices attached' 36 _ADB_DEVICES_HEADER = 'List of devices attached'
43 37
44 38
45 _logger = logging.getLogger() 39 _logger = logging.getLogger()
46 40
47 41
48 def _exit_if_needed(process): 42 def _exit_if_needed(process):
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
93 adb_path: Path to adb, optional if adb is in PATH. 87 adb_path: Path to adb, optional if adb is in PATH.
94 target_device: Device to run on, if multiple devices are connected. 88 target_device: Device to run on, if multiple devices are connected.
95 logcat_tags: Comma-separated list of additional logcat tags to use. 89 logcat_tags: Comma-separated list of additional logcat tags to use.
96 """ 90 """
97 91
98 def __init__(self, adb_path="adb", target_device=None, logcat_tags=None, 92 def __init__(self, adb_path="adb", target_device=None, logcat_tags=None,
99 verbose=False): 93 verbose=False):
100 self.adb_path = adb_path 94 self.adb_path = adb_path
101 self.target_device = target_device 95 self.target_device = target_device
102 self.stop_shell_registered = False 96 self.stop_shell_registered = False
103 self.adb_running_as_root = None
104 self.additional_logcat_tags = logcat_tags 97 self.additional_logcat_tags = logcat_tags
105 self.verbose_stdout = sys.stdout if verbose else open(os.devnull, 'w') 98 self.verbose_stdout = sys.stdout if verbose else open(os.devnull, 'w')
106 self.verbose_stderr = sys.stderr if verbose else self.verbose_stdout 99 self.verbose_stderr = sys.stderr if verbose else self.verbose_stdout
107 100
108 def _adb_command(self, args): 101 def _adb_command(self, args):
109 """Forms an adb command from the given arguments, prepending the adb path 102 """Forms an adb command from the given arguments, prepending the adb path
110 and adding a target device specifier, if needed. 103 and adding a target device specifier, if needed.
111 """ 104 """
112 adb_command = [self.adb_path] 105 adb_command = [self.adb_path]
113 if self.target_device: 106 if self.target_device:
114 adb_command.extend(['-s', self.target_device]) 107 adb_command.extend(['-s', self.target_device])
115 adb_command.extend(args) 108 adb_command.extend(args)
116 return adb_command 109 return adb_command
117 110
118 def _read_fifo(self, fifo_path, pipe, on_fifo_closed, max_attempts=5): 111 def _read_fifo(self, fifo_path, pipe, on_fifo_closed, max_attempts=5):
119 """Reads |fifo_path| on the device and write the contents to |pipe|. 112 """Reads |fifo_path| on the device and write the contents to |pipe|.
120 113
121 Calls |on_fifo_closed| when the fifo is closed. This method will try to find 114 Calls |on_fifo_closed| when the fifo is closed. This method will try to find
122 the path up to |max_attempts|, waiting 1 second between each attempt. If it 115 the path up to |max_attempts|, waiting 1 second between each attempt. If it
123 cannot find |fifo_path|, a exception will be raised. 116 cannot find |fifo_path|, a exception will be raised.
124 """ 117 """
125 fifo_command = self._adb_command( 118 fifo_command = self._adb_command(
126 ['shell', 'test -e "%s"; echo $?' % fifo_path]) 119 ['shell', 'run-as', _MOJO_SHELL_PACKAGE_NAME, 'ls', fifo_path])
127 120
128 def _run(): 121 def _run():
129 def _wait_for_fifo(): 122 def _wait_for_fifo():
130 for _ in xrange(max_attempts): 123 for _ in xrange(max_attempts):
131 if subprocess.check_output(fifo_command)[0] == '0': 124 output = subprocess.check_output(fifo_command).strip()
125 if output == fifo_path:
132 return 126 return
133 time.sleep(1) 127 time.sleep(1)
134 if on_fifo_closed: 128 if on_fifo_closed:
135 on_fifo_closed() 129 on_fifo_closed()
136 raise Exception("Unable to find fifo.") 130 raise Exception("Unable to find fifo.")
137 _wait_for_fifo() 131 _wait_for_fifo()
138 stdout_cat = subprocess.Popen( 132 stdout_cat = subprocess.Popen(
139 self._adb_command(['shell', 'cat', fifo_path]), stdout=pipe) 133 self._adb_command(['shell', 'run-as', _MOJO_SHELL_PACKAGE_NAME,
134 'cat', fifo_path]), stdout=pipe)
140 atexit.register(_exit_if_needed, stdout_cat) 135 atexit.register(_exit_if_needed, stdout_cat)
141 stdout_cat.wait() 136 stdout_cat.wait()
142 if on_fifo_closed: 137 if on_fifo_closed:
143 on_fifo_closed() 138 on_fifo_closed()
144 139
145 thread = threading.Thread(target=_run, name="StdoutRedirector") 140 thread = threading.Thread(target=_run, name="StdoutRedirector")
146 thread.start() 141 thread.start()
147 142
148 def _find_available_device_port(self): 143 def _find_available_device_port(self):
149 netstat_output = subprocess.check_output( 144 netstat_output = subprocess.check_output(
150 self._adb_command(['shell', 'netstat'])) 145 self._adb_command(['shell', 'netstat']))
151 return _find_available_port(netstat_output) 146 return _find_available_port(netstat_output)
152 147
153 def _forward_device_port_to_host(self, device_port, host_port): 148 def _forward_device_port_to_host(self, device_port, host_port):
154 """Maps the device port to the host port. If |device_port| is 0, a random 149 """Maps the device port to the host port. If |device_port| is 0, a random
155 available port is chosen. 150 available port is chosen.
156 151
157 Returns: 152 Returns:
158 The device port. 153 The device port.
159 """ 154 """
160 assert host_port 155 assert host_port
161 # Root is not required for `adb forward` (hence we don't check the return
162 # value), but if we can run adb as root, we have to do it now, because
163 # restarting adbd as root clears any port mappings. See
164 # https://github.com/domokit/devtools/issues/20.
165 self._run_adb_as_root()
166 156
167 if device_port == 0: 157 if device_port == 0:
168 # TODO(ppi): Should we have a retry loop to handle the unlikely races? 158 # TODO(ppi): Should we have a retry loop to handle the unlikely races?
169 device_port = self._find_available_device_port() 159 device_port = self._find_available_device_port()
170 subprocess.check_call(self._adb_command([ 160 subprocess.check_call(self._adb_command([
171 "reverse", "tcp:%d" % device_port, "tcp:%d" % host_port])) 161 "reverse", "tcp:%d" % device_port, "tcp:%d" % host_port]))
172 162
173 def _unmap_port(): 163 def _unmap_port():
174 unmap_command = self._adb_command([ 164 unmap_command = self._adb_command([
175 "reverse", "--remove", "tcp:%d" % device_port]) 165 "reverse", "--remove", "tcp:%d" % device_port])
176 subprocess.Popen(unmap_command) 166 subprocess.Popen(unmap_command)
177 atexit.register(_unmap_port) 167 atexit.register(_unmap_port)
178 return device_port 168 return device_port
179 169
180 def _forward_host_port_to_device(self, host_port, device_port): 170 def _forward_host_port_to_device(self, host_port, device_port):
181 """Maps the host port to the device port. If |host_port| is 0, a random 171 """Maps the host port to the device port. If |host_port| is 0, a random
182 available port is chosen. 172 available port is chosen.
183 173
184 Returns: 174 Returns:
185 The host port. 175 The host port.
186 """ 176 """
187 assert device_port 177 assert device_port
188 self._run_adb_as_root()
189 178
190 if host_port == 0: 179 if host_port == 0:
191 # TODO(ppi): Should we have a retry loop to handle the unlikely races? 180 # TODO(ppi): Should we have a retry loop to handle the unlikely races?
192 host_port = _find_available_host_port() 181 host_port = _find_available_host_port()
193 subprocess.check_call(self._adb_command([ 182 subprocess.check_call(self._adb_command([
194 "forward", 'tcp:%d' % host_port, 'tcp:%d' % device_port])) 183 "forward", 'tcp:%d' % host_port, 'tcp:%d' % device_port]))
195 184
196 def _unmap_port(): 185 def _unmap_port():
197 unmap_command = self._adb_command([ 186 unmap_command = self._adb_command([
198 "forward", "--remove", "tcp:%d" % device_port]) 187 "forward", "--remove", "tcp:%d" % device_port])
199 subprocess.Popen(unmap_command) 188 subprocess.Popen(unmap_command)
200 atexit.register(_unmap_port) 189 atexit.register(_unmap_port)
201 return host_port 190 return host_port
202 191
203 def _run_adb_as_root(self):
204 if self.adb_running_as_root is not None:
205 return self.adb_running_as_root
206
207 if ('cannot run as root' not in subprocess.check_output(
208 self._adb_command(['root']))):
209 # Wait for adbd to restart.
210 subprocess.check_call(
211 self._adb_command(['wait-for-device']),
212 stdout=self.verbose_stdout, stderr=self.verbose_stderr)
213 self.adb_running_as_root = True
214 else:
215 self.adb_running_as_root = False
216
217 return self.adb_running_as_root
218
219 def _is_shell_package_installed(self): 192 def _is_shell_package_installed(self):
220 # Adb should print one line if the package is installed and return empty 193 # Adb should print one line if the package is installed and return empty
221 # string otherwise. 194 # string otherwise.
222 return len(subprocess.check_output(self._adb_command([ 195 return len(subprocess.check_output(self._adb_command([
223 'shell', 'pm', 'list', 'packages', _MOJO_SHELL_PACKAGE_NAME]))) > 0 196 'shell', 'pm', 'list', 'packages', _MOJO_SHELL_PACKAGE_NAME]))) > 0
224 197
225 @staticmethod 198 @staticmethod
226 def get_tmp_dir_path(): 199 def get_tmp_dir_path():
227 """Returns a path to a cache directory owned by the shell where temporary 200 """Returns a path to a cache directory owned by the shell where temporary
228 files can be stored. 201 files can be stored.
229 """ 202 """
230 return '/data/data/%s/cache/tmp/' % _MOJO_SHELL_PACKAGE_NAME 203 return '/data/data/%s/cache/tmp/' % _MOJO_SHELL_PACKAGE_NAME
231 204
232 def pull_file(self, device_path, destination_path, remove_original=False): 205 def pull_file(self, device_path, destination_path, remove_original=False):
233 """Copies or moves the specified file on the device to the host.""" 206 """Copies or moves the specified file on the device to the host."""
234 subprocess.check_call(self._adb_command([ 207 subprocess.check_call(self._adb_command([
235 'pull', device_path, destination_path])) 208 'pull', device_path, destination_path]))
236 if remove_original: 209 if remove_original:
237 subprocess.check_call(self._adb_command([ 210 subprocess.check_call(self._adb_command([
238 'shell', 'rm', device_path])) 211 'shell', 'rm', device_path]))
239 212
240 def check_device(self, require_root=False): 213 def check_device(self):
241 """Verifies if the device configuration allows adb to run. 214 """Verifies if the device configuration allows adb to run.
242 215
243 If a target device was indicated in the constructor, it checks that the 216 If a target device was indicated in the constructor, it checks that the
244 device is available. Otherwise, it checks that there is exactly one 217 device is available. Otherwise, it checks that there is exactly one
245 available device. 218 available device.
246 219
247 Returns: 220 Returns:
248 A tuple of (result, msg). |result| is True iff if the device is correctly 221 A tuple of (result, msg). |result| is True iff if the device is correctly
249 configured and False otherwise. |msg| is the reason for failure if 222 configured and False otherwise. |msg| is the reason for failure if
250 |result| is False and None otherwise. 223 |result| is False and None otherwise.
(...skipping 13 matching lines...) Expand all
264 return False, ('Cannot connect to the selected device, status: ' + 237 return False, ('Cannot connect to the selected device, status: ' +
265 devices[self.target_device]) 238 devices[self.target_device])
266 239
267 if len(devices) > 1: 240 if len(devices) > 1:
268 return False, ('More than one device connected and target device not ' 241 return False, ('More than one device connected and target device not '
269 'specified.') 242 'specified.')
270 243
271 if not devices.itervalues().next() == 'device': 244 if not devices.itervalues().next() == 'device':
272 return False, 'Connected device is not available.' 245 return False, 'Connected device is not available.'
273 246
274 if require_root and not self._run_adb_as_root():
275 return False, 'Cannot run on an unrooted device.'
276
277 return True, None 247 return True, None
278 248
279 def install_apk(self, shell_apk_path): 249 def install_apk(self, shell_apk_path):
280 """Installs the apk on the device. 250 """Installs the apk on the device.
281 251
282 This method computes checksum of the APK and skips the installation if the 252 This method computes checksum of the APK and skips the installation if the
283 fingerprint matches the one saved on the device upon the previous 253 fingerprint matches the one saved on the device upon the previous
284 installation. 254 installation.
285 255
286 Args: 256 Args:
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
330 STDOUT_PIPE = "/data/data/%s/stdout.fifo" % _MOJO_SHELL_PACKAGE_NAME 300 STDOUT_PIPE = "/data/data/%s/stdout.fifo" % _MOJO_SHELL_PACKAGE_NAME
331 301
332 cmd = self._adb_command(['shell', 'am', 'start', 302 cmd = self._adb_command(['shell', 'am', 'start',
333 '-S', 303 '-S',
334 '-a', 'android.intent.action.VIEW', 304 '-a', 'android.intent.action.VIEW',
335 '-n', '%s/.MojoShellActivity' % 305 '-n', '%s/.MojoShellActivity' %
336 _MOJO_SHELL_PACKAGE_NAME]) 306 _MOJO_SHELL_PACKAGE_NAME])
337 307
338 parameters = [] 308 parameters = []
339 if stdout or on_application_stop: 309 if stdout or on_application_stop:
340 # We need to run as root to access the fifo file we use for stdout 310 # Remove any leftover fifo file after the previous run.
341 # redirection. 311 subprocess.check_call(self._adb_command(
342 if self._run_adb_as_root(): 312 ['shell', 'run-as', _MOJO_SHELL_PACKAGE_NAME,
343 # Remove any leftover fifo file after the previous run. 313 'rm', '-f', STDOUT_PIPE]))
344 subprocess.check_call(self._adb_command(
345 ['shell', 'rm', '-f', STDOUT_PIPE]))
346 314
347 parameters.append('--fifo-path=%s' % STDOUT_PIPE) 315 parameters.append('--fifo-path=%s' % STDOUT_PIPE)
348 self._read_fifo(STDOUT_PIPE, stdout, on_application_stop) 316 self._read_fifo(STDOUT_PIPE, stdout, on_application_stop)
349 else:
350 _logger.warning("Running without root access, full stdout of the "
351 "shell won't be available.")
352 parameters.extend(arguments) 317 parameters.extend(arguments)
353 318
354 if parameters: 319 if parameters:
355 device_filename = ( 320 device_filename = (
356 '/sdcard/%s/args_%s' % (_MOJO_SHELL_PACKAGE_NAME, str(uuid.uuid4()))) 321 '/sdcard/%s/args_%s' % (_MOJO_SHELL_PACKAGE_NAME, str(uuid.uuid4())))
357 with tempfile.NamedTemporaryFile(delete=False) as temp: 322 with tempfile.NamedTemporaryFile(delete=False) as temp:
358 try: 323 try:
359 for parameter in parameters: 324 for parameter in parameters:
360 temp.write(parameter) 325 temp.write(parameter)
361 temp.write('\n') 326 temp.write('\n')
(...skipping 13 matching lines...) Expand all
375 """Stops the mojo shell.""" 340 """Stops the mojo shell."""
376 subprocess.check_call(self._adb_command(['shell', 341 subprocess.check_call(self._adb_command(['shell',
377 'am', 342 'am',
378 'force-stop', 343 'force-stop',
379 _MOJO_SHELL_PACKAGE_NAME])) 344 _MOJO_SHELL_PACKAGE_NAME]))
380 345
381 def clean_logs(self): 346 def clean_logs(self):
382 """Cleans the logs on the device.""" 347 """Cleans the logs on the device."""
383 subprocess.check_call(self._adb_command(['logcat', '-c'])) 348 subprocess.check_call(self._adb_command(['logcat', '-c']))
384 349
385 def show_logs(self, include_native_logs=True): 350 def show_logs(self):
386 """Displays the log for the mojo shell. 351 """Displays the log for the mojo shell.
387 352
388 Returns: 353 Returns:
389 The process responsible for reading the logs. 354 The process responsible for reading the logs.
390 """ 355 """
391 tags = _LOGCAT_JAVA_TAGS 356 tags = _LOGCAT_JAVA_TAGS
392 if include_native_logs:
393 tags.extend(_LOGCAT_NATIVE_TAGS)
394 if self.additional_logcat_tags is not None: 357 if self.additional_logcat_tags is not None:
395 tags.extend(self.additional_logcat_tags.split(",")) 358 tags.extend(self.additional_logcat_tags.split(","))
396 logcat = subprocess.Popen( 359 logcat = subprocess.Popen(
397 self._adb_command(['logcat', '-s', ' '.join(tags)]), 360 self._adb_command(['logcat', '-s', ' '.join(tags)]),
398 stdout=sys.stdout) 361 stdout=sys.stdout)
399 atexit.register(_exit_if_needed, logcat) 362 atexit.register(_exit_if_needed, logcat)
400 return logcat 363 return logcat
401 364
402 def forward_observatory_ports(self): 365 def forward_observatory_ports(self):
403 """Forwards the ports used by the dart observatories to the host machine. 366 """Forwards the ports used by the dart observatories to the host machine.
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
438 401
439 @overrides(Shell) 402 @overrides(Shell)
440 def forward_host_port_to_shell(self, host_port): 403 def forward_host_port_to_shell(self, host_port):
441 self._forward_host_port_to_device(host_port, host_port) 404 self._forward_host_port_to_device(host_port, host_port)
442 405
443 @overrides(Shell) 406 @overrides(Shell)
444 def run(self, arguments): 407 def run(self, arguments):
445 self.clean_logs() 408 self.clean_logs()
446 self.forward_observatory_ports() 409 self.forward_observatory_ports()
447 410
448 # If we are running as root, don't carry over the native logs from logcat - 411 p = self.show_logs();
449 # we will have these in the stdout.
450 p = self.show_logs(include_native_logs=(not self._run_adb_as_root()))
451 self.start_shell(arguments, sys.stdout, p.terminate) 412 self.start_shell(arguments, sys.stdout, p.terminate)
452 p.wait() 413 p.wait()
453 return None 414 return None
454 415
455 @overrides(Shell) 416 @overrides(Shell)
456 def run_and_get_output(self, arguments, timeout=None): 417 def run_and_get_output(self, arguments, timeout=None):
457 class Results: 418 class Results:
458 """Workaround for Python scoping rules that prevent assigning to variables 419 """Workaround for Python scoping rules that prevent assigning to variables
459 from the outer scope. 420 from the outer scope.
460 """ 421 """
461 output = None 422 output = None
462 423
463 def do_run(): 424 def do_run():
464 (r, w) = os.pipe() 425 (r, w) = os.pipe()
465 with os.fdopen(r, "r") as rf: 426 with os.fdopen(r, "r") as rf:
466 with os.fdopen(w, "w") as wf: 427 with os.fdopen(w, "w") as wf:
467 self.start_shell(arguments, wf, wf.close) 428 self.start_shell(arguments, wf, wf.close)
468 Results.output = rf.read() 429 Results.output = rf.read()
469 430
470 run_thread = threading.Thread(target=do_run) 431 run_thread = threading.Thread(target=do_run)
471 run_thread.start() 432 run_thread.start()
472 run_thread.join(timeout) 433 run_thread.join(timeout)
473 434
474 if run_thread.is_alive(): 435 if run_thread.is_alive():
475 self.stop_shell() 436 self.stop_shell()
476 return None, Results.output, True 437 return None, Results.output, True
477 return None, Results.output, False 438 return None, Results.output, False
OLDNEW
« no previous file with comments | « no previous file | mojo/devtools/common/devtoolslib/shell_arguments.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698