| # Copyright 2018 The Chromium Authors. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 | import logging | 
 | import os | 
 | import platform | 
 | import signal | 
 | import socket | 
 | import subprocess | 
 | import sys | 
 | import time | 
 | import threading | 
 |  | 
 | DIR_SOURCE_ROOT = os.path.abspath( | 
 |     os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) | 
 | IMAGES_ROOT = os.path.join( | 
 |     DIR_SOURCE_ROOT, 'third_party', 'fuchsia-sdk', 'images') | 
 | SDK_ROOT = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'fuchsia-sdk', 'sdk') | 
 |  | 
 | def EnsurePathExists(path): | 
 |   """Checks that the file |path| exists on the filesystem and returns the path | 
 |   if it does, raising an exception otherwise.""" | 
 |  | 
 |   if not os.path.exists(path): | 
 |     raise IOError('Missing file: ' + path) | 
 |  | 
 |   return path | 
 |  | 
 | def GetHostOsFromPlatform(): | 
 |   host_platform = sys.platform | 
 |   if host_platform.startswith('linux'): | 
 |     return 'linux' | 
 |   elif host_platform.startswith('darwin'): | 
 |     return 'mac' | 
 |   raise Exception('Unsupported host platform: %s' % host_platform) | 
 |  | 
 | def GetHostArchFromPlatform(): | 
 |   host_arch = platform.machine() | 
 |   if host_arch == 'x86_64': | 
 |     return 'x64' | 
 |   elif host_arch == 'aarch64': | 
 |     return 'arm64' | 
 |   raise Exception('Unsupported host architecture: %s' % host_arch) | 
 |  | 
 | def GetHostToolPathFromPlatform(tool): | 
 |   host_arch = platform.machine() | 
 |   return os.path.join(SDK_ROOT, 'tools', GetHostArchFromPlatform(), tool) | 
 |  | 
 |  | 
 | def GetEmuRootForPlatform(emulator): | 
 |   return os.path.join( | 
 |       DIR_SOURCE_ROOT, 'third_party', '{0}-{1}-{2}'.format( | 
 |           emulator, GetHostOsFromPlatform(), GetHostArchFromPlatform())) | 
 |  | 
 |  | 
 | def ConnectPortForwardingTask(target, local_port, remote_port = 0): | 
 |   """Establishes a port forwarding SSH task to a localhost TCP endpoint hosted | 
 |   at port |local_port|. Blocks until port forwarding is established. | 
 |  | 
 |   Returns the remote port number.""" | 
 |  | 
 |   forwarding_flags = ['-O', 'forward',  # Send SSH mux control signal. | 
 |                       '-R', '%d:localhost:%d' % (remote_port, local_port), | 
 |                       '-v',   # Get forwarded port info from stderr. | 
 |                       '-NT']  # Don't execute command; don't allocate terminal. | 
 |  | 
 |   if remote_port != 0: | 
 |     # Forward to a known remote port. | 
 |     task = target.RunCommand([], ssh_args=forwarding_flags) | 
 |     if task.returncode != 0: | 
 |       raise Exception('Could not establish a port forwarding connection.') | 
 |     return | 
 |  | 
 |   task = target.RunCommandPiped([], | 
 |                                 ssh_args=forwarding_flags, | 
 |                                 stdout=subprocess.PIPE, | 
 |                                 stderr=open('/dev/null')) | 
 |   output = task.stdout.readlines() | 
 |   task.wait() | 
 |   if task.returncode != 0: | 
 |     raise Exception('Got an error code when requesting port forwarding: %d' % | 
 |                     task.returncode) | 
 |  | 
 |   parsed_port = int(output[0].strip()) | 
 |   logging.debug('Port forwarding established (local=%d, device=%d)' % | 
 |                 (local_port, parsed_port)) | 
 |   return parsed_port | 
 |  | 
 |  | 
 | def GetAvailableTcpPort(): | 
 |   """Finds a (probably) open port by opening and closing a listen socket.""" | 
 |   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
 |   sock.bind(("", 0)) | 
 |   port = sock.getsockname()[1] | 
 |   sock.close() | 
 |   return port | 
 |  | 
 |  | 
 | def SubprocessCallWithTimeout(command, silent=False, timeout_secs=None): | 
 |   """Helper function for running a command. | 
 |  | 
 |   Args: | 
 |     command: The command to run. | 
 |     silent: If true, stdout and stderr of the command will not be printed. | 
 |     timeout_secs: Maximum amount of time allowed for the command to finish. | 
 |  | 
 |   Returns: | 
 |     A tuple of (return code, stdout, stderr) of the command. Raises | 
 |     an exception if the subprocess times out. | 
 |   """ | 
 |  | 
 |   if silent: | 
 |     devnull = open(os.devnull, 'w') | 
 |     process = subprocess.Popen(command, stdout=devnull, stderr=devnull) | 
 |   else: | 
 |     process = subprocess.Popen(command, | 
 |                                stdout=subprocess.PIPE, | 
 |                                stderr=subprocess.PIPE) | 
 |   timeout_timer = None | 
 |   if timeout_secs: | 
 |  | 
 |     def interrupt_process(): | 
 |       process.send_signal(signal.SIGKILL) | 
 |  | 
 |     timeout_timer = threading.Timer(timeout_secs, interrupt_process) | 
 |  | 
 |     # Ensure that keyboard interrupts are handled properly (crbug/1198113). | 
 |     timeout_timer.daemon = True | 
 |  | 
 |     timeout_timer.start() | 
 |  | 
 |   out, err = process.communicate() | 
 |   if timeout_timer: | 
 |     timeout_timer.cancel() | 
 |  | 
 |   if process.returncode == -9: | 
 |     raise Exception('Timeout when executing \"%s\".' % ' '.join(command)) | 
 |  | 
 |   return process.returncode, out, err |