summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/android/pylib/base_test_runner.py22
-rw-r--r--build/android/pylib/base_test_sharder.py9
-rw-r--r--build/android/pylib/chrome_test_server_spawner.py6
-rw-r--r--build/android/pylib/cmd_helper.py19
-rw-r--r--build/android/pylib/forwarder.py244
-rw-r--r--build/android/pylib/ports.py28
-rw-r--r--build/android/pylib/run_java_tests.py6
-rwxr-xr-xbuild/android/run_tests.py2
-rw-r--r--tools/android/common/daemon.cc1
-rw-r--r--tools/android/forwarder2/command.cc12
-rw-r--r--tools/android/forwarder2/common.cc28
-rw-r--r--tools/android/forwarder2/common.h89
-rw-r--r--tools/android/forwarder2/daemon.cc256
-rw-r--r--tools/android/forwarder2/daemon.h38
-rw-r--r--tools/android/forwarder2/device_controller.cc11
-rw-r--r--tools/android/forwarder2/device_listener.cc7
-rw-r--r--tools/android/forwarder2/forwarder.gyp3
-rw-r--r--tools/android/forwarder2/host_controller.cc9
-rw-r--r--tools/android/forwarder2/host_forwarder_main.cc301
-rw-r--r--tools/android/forwarder2/socket.cc59
-rw-r--r--tools/android/forwarder2/socket.h26
21 files changed, 869 insertions, 307 deletions
diff --git a/build/android/pylib/base_test_runner.py b/build/android/pylib/base_test_runner.py
index 619bc6e..5355633 100644
--- a/build/android/pylib/base_test_runner.py
+++ b/build/android/pylib/base_test_runner.py
@@ -82,7 +82,7 @@ class BaseTestRunner(object):
def SetUp(self):
"""Called before tests run."""
- pass
+ Forwarder.KillDevice(self.adb)
def HasTests(self):
"""Whether the test suite has tests to run."""
@@ -129,6 +129,13 @@ class BaseTestRunner(object):
self.StartForwarderForHttpServer()
return (self._forwarder_device_port, self._http_server.port)
+ def _CreateAndRunForwarder(
+ self, adb, port_pairs, tool, host_name, build_type):
+ """Creates and run a forwarder."""
+ forwarder = Forwarder(adb, build_type)
+ forwarder.Run(port_pairs, tool, host_name)
+ return forwarder
+
def StartForwarder(self, port_pairs):
"""Starts TCP traffic forwarding for the given |port_pairs|.
@@ -137,7 +144,7 @@ class BaseTestRunner(object):
"""
if self._forwarder:
self._forwarder.Close()
- self._forwarder = Forwarder(
+ self._forwarder = self._CreateAndRunForwarder(
self.adb, port_pairs, self.tool, '127.0.0.1', self.build_type)
def StartForwarderForHttpServer(self):
@@ -162,14 +169,7 @@ class BaseTestRunner(object):
# Forwarders should be killed before the actual servers they're forwarding
# to as they are clients potentially with open connections and to allow for
# proper hand-shake/shutdown.
- if self._forwarder or self._spawner_forwarder:
- # Kill all forwarders on the device and then kill the process on the host
- # (if it exists)
- self.adb.KillAll('device_forwarder')
- if self._forwarder:
- self._forwarder.Close()
- if self._spawner_forwarder:
- self._spawner_forwarder.Close()
+ Forwarder.KillDevice(self.adb)
if self._http_server:
self._http_server.ShutdownHttpServer()
if self._spawning_server:
@@ -204,7 +204,7 @@ class BaseTestRunner(object):
logging.error(';'.join(error_msgs))
raise Exception('Can not start the test spawner server.')
self._PushTestServerPortInfoToDevice()
- self._spawner_forwarder = Forwarder(
+ self._spawner_forwarder = self._CreateAndRunForwarder(
self.adb,
[(self.test_server_spawner_port, self.test_server_spawner_port)],
self.tool, '127.0.0.1', self.build_type)
diff --git a/build/android/pylib/base_test_sharder.py b/build/android/pylib/base_test_sharder.py
index b8d03c83..fa05abec 100644
--- a/build/android/pylib/base_test_sharder.py
+++ b/build/android/pylib/base_test_sharder.py
@@ -8,6 +8,7 @@ import logging
import multiprocessing
from android_commands import errors
+from forwarder import Forwarder
from test_result import TestResults
@@ -24,6 +25,7 @@ def _ShardedTestRunnable(test):
except SystemExit:
return TestResults()
+
def SetTestsContainer(tests_container):
"""Sets tests container.
@@ -42,12 +44,13 @@ class BaseTestSharder(object):
# See more in SetTestsContainer.
tests_container = None
- def __init__(self, attached_devices):
+ def __init__(self, attached_devices, build_type='Debug'):
self.attached_devices = attached_devices
# Worst case scenario: a device will drop offline per run, so we need
# to retry until we're out of devices.
self.retries = len(self.attached_devices)
self.tests = []
+ self.build_type = build_type
def CreateShardedTestRunner(self, device, index):
"""Factory function to create a suite-specific test runner.
@@ -63,11 +66,11 @@ class BaseTestSharder(object):
def SetupSharding(self, tests):
"""Called before starting the shards."""
- pass
+ Forwarder.KillHost(self.build_type)
def OnTestsCompleted(self, test_runners, test_results):
"""Notifies that we completed the tests."""
- pass
+ Forwarder.KillHost(self.build_type)
def RunShardedTests(self):
"""Runs the tests in all connected devices.
diff --git a/build/android/pylib/chrome_test_server_spawner.py b/build/android/pylib/chrome_test_server_spawner.py
index 512a609..8206ca0 100644
--- a/build/android/pylib/chrome_test_server_spawner.py
+++ b/build/android/pylib/chrome_test_server_spawner.py
@@ -217,9 +217,9 @@ class TestServerThread(threading.Thread):
else:
self.is_ready = _CheckPortStatus(self.host_port, True)
if self.is_ready:
- self._test_server_forwarder = Forwarder(
- self.adb, [(0, self.host_port)], self.tool, '127.0.0.1',
- self.build_type)
+ self._test_server_forwarder = Forwarder(self.adb, self.build_type)
+ self._test_server_forwarder.Run(
+ [(0, self.host_port)], self.tool, '127.0.0.1')
# Check whether the forwarder is ready on the device.
self.is_ready = False
device_port = self._test_server_forwarder.DevicePortForHostPort(
diff --git a/build/android/pylib/cmd_helper.py b/build/android/pylib/cmd_helper.py
index 8b50130..46b6981 100644
--- a/build/android/pylib/cmd_helper.py
+++ b/build/android/pylib/cmd_helper.py
@@ -40,11 +40,28 @@ def GetCmdOutput(args, cwd=None, shell=False):
Captures and returns the command's stdout.
Prints the command's stderr to logger (which defaults to stdout).
"""
+ (_, output) = GetCmdStatusAndOutput(args, cwd, shell)
+ return output
+
+def GetCmdStatusAndOutput(args, cwd=None, shell=False):
+ """Executes a subprocess and returns its exit code and output.
+
+ Args:
+ args: A string or a sequence of program arguments. The program to execute is
+ the string or the first item in the args sequence.
+ cwd: If not None, the subprocess's current directory will be changed to
+ |cwd| before it's executed.
+ shell: Whether to execute args as a shell command.
+
+ Returns:
+ The tuple (exit code, output).
+ """
logging.info(str(args) + ' ' + (cwd or ''))
p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=shell)
stdout, stderr = p.communicate()
+ exit_code = p.returncode
if stderr:
logging.critical(stderr)
logging.info(stdout[:4096]) # Truncate output longer than 4k.
- return stdout
+ return (exit_code, stdout)
diff --git a/build/android/pylib/forwarder.py b/build/android/pylib/forwarder.py
index bc41db3..a0dc5f2 100644
--- a/build/android/pylib/forwarder.py
+++ b/build/android/pylib/forwarder.py
@@ -15,6 +15,11 @@ import ports
from pylib import pexpect
+
+def _MakeBinaryPath(build_type, binary_name):
+ return os.path.join(constants.CHROME_DIR, 'out', build_type, binary_name)
+
+
class Forwarder(object):
"""Class to manage port forwards from the device to the host."""
@@ -24,13 +29,27 @@ class Forwarder(object):
_DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder'
_TIMEOUT_SECS = 30
- def __init__(self, adb, port_pairs, tool, host_name, build_type):
+ def __init__(self, adb, build_type):
"""Forwards TCP ports on the device back to the host.
Works like adb forward, but in reverse.
Args:
adb: Instance of AndroidCommands for talking to the device.
+ build_type: 'Release' or 'Debug'.
+ """
+ assert build_type in ('Release', 'Debug')
+ self._adb = adb
+ self._host_to_device_port_map = dict()
+ self._device_process = None
+ self._host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder')
+ self._device_forwarder_path = _MakeBinaryPath(
+ build_type, 'device_forwarder')
+
+ def Run(self, port_pairs, tool, host_name):
+ """Runs the forwarder.
+
+ Args:
port_pairs: A list of tuples (device_port, host_port) to forward. Note
that you can specify 0 as a device_port, in which case a
port will by dynamically assigned on the device. You can
@@ -40,159 +59,104 @@ class Forwarder(object):
forwarder (see valgrind_tools.py).
host_name: Address to forward to, must be addressable from the
host machine. Usually use loopback '127.0.0.1'.
- build_type: 'Release' or 'Debug'.
Raises:
Exception on failure to forward the port.
"""
- self._adb = adb
- self._host_to_device_port_map = dict()
- self._host_process = None
- self._device_process = None
- self._adb_forward_process = None
-
- self._host_adb_control_port = ports.AllocateTestServerPort()
- if not self._host_adb_control_port:
+ host_adb_control_port = ports.AllocateTestServerPort()
+ if not host_adb_control_port:
raise Exception('Failed to allocate a TCP port in the host machine.')
- adb.PushIfNeeded(
- os.path.join(constants.CHROME_DIR, 'out', build_type,
- 'device_forwarder'),
- Forwarder._DEVICE_FORWARDER_PATH)
- self._host_forwarder_path = os.path.join(constants.CHROME_DIR,
- 'out',
- build_type,
- 'host_forwarder')
- forward_string = ['%d:%d:%s' %
- (device, host, host_name) for device, host in port_pairs]
- logging.info('Forwarding ports: %s', forward_string)
- timeout_sec = 5
- host_pattern = 'host_forwarder.*' + ' '.join(forward_string)
- # TODO(felipeg): Rather than using a blocking kill() here, the device
- # forwarder could try to bind the Unix Domain Socket until it succeeds or
- # while it fails because the socket is already bound (with appropriate
- # timeout handling obviously).
- self._KillHostForwarderBlocking(host_pattern, timeout_sec)
- self._KillDeviceForwarderBlocking(timeout_sec)
- self._adb_forward_process = pexpect.spawn(
- 'adb', ['-s',
- adb._adb.GetSerialNumber(),
- 'forward',
- 'tcp:%s' % self._host_adb_control_port,
- 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT])
- self._device_process = pexpect.spawn(
- 'adb', ['-s',
- adb._adb.GetSerialNumber(),
- 'shell',
- '%s %s -D --adb_sock=%s' % (
- tool.GetUtilWrapper(),
- Forwarder._DEVICE_FORWARDER_PATH,
- Forwarder._DEVICE_ADB_CONTROL_PORT)])
-
- device_success_re = re.compile('Starting Device Forwarder.')
- device_failure_re = re.compile('.*:ERROR:(.*)')
- index = self._device_process.expect([device_success_re,
- device_failure_re,
- pexpect.EOF,
- pexpect.TIMEOUT],
- Forwarder._TIMEOUT_SECS)
- if index == 1:
- # Failure
- error_msg = str(self._device_process.match.group(1))
- logging.error(self._device_process.before)
- self._CloseProcess()
- raise Exception('Failed to start Device Forwarder with Error: %s' %
- error_msg)
- elif index == 2:
- logging.error(self._device_process.before)
- self._CloseProcess()
- raise Exception('Unexpected EOF while trying to start Device Forwarder.')
- elif index == 3:
- logging.error(self._device_process.before)
- self._CloseProcess()
- raise Exception('Timeout while trying start Device Forwarder')
-
- self._host_process = pexpect.spawn(self._host_forwarder_path,
- ['--adb_port=%s' % (
- self._host_adb_control_port)] +
- forward_string)
-
- # Read the output of the command to determine which device ports where
- # forwarded to which host ports (necessary if
- host_success_re = re.compile('Forwarding device port (\d+) to host (\d+):')
- host_failure_re = re.compile('Couldn\'t start forwarder server for port '
- 'spec: (\d+):(\d+)')
- for pair in port_pairs:
- index = self._host_process.expect([host_success_re,
- host_failure_re,
- pexpect.EOF,
- pexpect.TIMEOUT],
- Forwarder._TIMEOUT_SECS)
- if index == 0:
- # Success
- device_port = int(self._host_process.match.group(1))
- host_port = int(self._host_process.match.group(2))
- self._host_to_device_port_map[host_port] = device_port
- logging.info("Forwarding device port: %d to host port: %d." %
- (device_port, host_port))
- elif index == 1:
- # Failure
- device_port = int(self._host_process.match.group(1))
- host_port = int(self._host_process.match.group(2))
- self._CloseProcess()
- raise Exception('Failed to forward port %d to %d' % (device_port,
- host_port))
+ self._adb.PushIfNeeded(self._device_forwarder_path,
+ Forwarder._DEVICE_FORWARDER_PATH)
+ redirection_commands = [
+ '%d:%d:%d:%s' % (host_adb_control_port, device, host,
+ host_name) for device, host in port_pairs]
+ logging.info('Command format: <ADB port>:<Device port>' +
+ '[:<Forward to port>:<Forward to address>]')
+ logging.info('Forwarding using commands: %s', redirection_commands)
+ if cmd_helper.RunCmd(
+ ['adb', '-s', self._adb._adb.GetSerialNumber(), 'forward',
+ 'tcp:%s' % host_adb_control_port,
+ 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) != 0:
+ raise Exception('Error while running adb forward.')
+
+ if not self._adb.ExtractPid('device_forwarder'):
+ # TODO(pliard): Get rid of pexpect here and make device_forwarder a daemon
+ # with a blocking CLI process that exits with a proper exit code and not
+ # while the daemon is still setting up. This would be similar to how
+ # host_forwarder works.
+ self._device_process = pexpect.spawn(
+ 'adb', ['-s',
+ self._adb._adb.GetSerialNumber(),
+ 'shell',
+ '%s %s -D --adb_sock=%s' % (
+ tool.GetUtilWrapper(),
+ Forwarder._DEVICE_FORWARDER_PATH,
+ Forwarder._DEVICE_ADB_CONTROL_PORT)])
+ device_success_re = re.compile('Starting Device Forwarder.')
+ device_failure_re = re.compile('.*:ERROR:(.*)')
+ index = self._device_process.expect([device_success_re,
+ device_failure_re,
+ pexpect.EOF,
+ pexpect.TIMEOUT],
+ Forwarder._TIMEOUT_SECS)
+ if index == 1:
+ error_msg = str(self._device_process.match.group(1))
+ logging.error(self._device_process.before)
+ self._device_process.close()
+ raise Exception('Failed to start Device Forwarder with Error: %s' %
+ error_msg)
elif index == 2:
- logging.error(self._host_process.before)
- self._CloseProcess()
- raise Exception('Unexpected EOF while trying to forward ports %s' %
- port_pairs)
+ logging.error(self._device_process.before)
+ self._device_process.close()
+ raise Exception(
+ 'Unexpected EOF while trying to start Device Forwarder.')
elif index == 3:
- logging.error(self._host_process.before)
- self._CloseProcess()
- raise Exception('Timeout while trying to forward ports %s' % port_pairs)
-
- def _KillHostForwarderBlocking(self, host_pattern, timeout_sec):
- """Kills any existing host forwarders using the provided pattern.
-
- Note that this waits until the process terminates.
- """
- cmd_helper.RunCmd(['pkill', '-f', host_pattern])
- elapsed = 0
- wait_period = 0.1
- while not cmd_helper.RunCmd(['pgrep', '-f', host_pattern]) and (
- elapsed < timeout_sec):
- time.sleep(wait_period)
- elapsed += wait_period
- if elapsed >= timeout_sec:
- raise Exception('Timed out while killing ' + host_pattern)
-
- def _KillDeviceForwarderBlocking(self, timeout_sec):
- """Kills any existing device forwarders.
-
- Note that this waits until the process terminates.
- """
- processes_killed = self._adb.KillAllBlocking(
- 'device_forwarder', timeout_sec)
+ logging.error(self._device_process.before)
+ self._device_process.close()
+ raise Exception('Timeout while trying start Device Forwarder')
+
+ for redirection_command in redirection_commands:
+ (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
+ [self._host_forwarder_path, redirection_command])
+ if exit_code != 0:
+ raise Exception('%s exited with %d: %s' % (self._host_forwarder_path,
+ exit_code, output))
+ tokens = output.split(':')
+ if len(tokens) != 2:
+ raise Exception('Unexpected host forwarder output "%s", ' +
+ 'expected "device_port:host_port"' % output)
+ device_port = int(tokens[0])
+ host_port = int(tokens[1])
+ self._host_to_device_port_map[host_port] = device_port
+ logging.info('Forwarding device port: %d to host port: %d.', device_port,
+ host_port)
+
+ @staticmethod
+ def KillHost(build_type):
+ host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder')
+ (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
+ [host_forwarder_path, 'kill-server'])
+ if exit_code != 0:
+ raise Exception('%s exited with %d: %s' % (host_forwarder_path,
+ exit_code, output))
+
+ @staticmethod
+ def KillDevice(adb):
+ logging.info('Killing device_forwarder.')
+ timeout_sec = 5
+ processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec)
if not processes_killed:
- pids = self._adb.ExtractPid('device_forwarder')
+ pids = adb.ExtractPid('device_forwarder')
if pids:
raise Exception('Timed out while killing device_forwarder')
- def _CloseProcess(self):
- if self._host_process:
- self._host_process.close()
- if self._device_process:
- self._device_process.close()
- if self._adb_forward_process:
- self._adb_forward_process.close()
- self._host_process = None
- self._device_process = None
- self._adb_forward_process = None
-
def DevicePortForHostPort(self, host_port):
"""Get the device port that corresponds to a given host port."""
return self._host_to_device_port_map.get(host_port)
def Close(self):
"""Terminate the forwarder process."""
- self._CloseProcess()
+ if self._device_process:
+ self._device_process.close()
+ self._device_process = None
diff --git a/build/android/pylib/ports.py b/build/android/pylib/ports.py
index 06b9f58..74c84c1 100644
--- a/build/android/pylib/ports.py
+++ b/build/android/pylib/ports.py
@@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-"""Functions that deals with local and device ports."""
+"""Functions that deal with local and device ports."""
import contextlib
import fcntl
@@ -17,12 +17,13 @@ import cmd_helper
import constants
-#The following two methods are used to allocate the port source for various
-# types of test servers. Because some net relates tests can be run on shards
-# at same time, it's important to have a mechanism to allocate the port process
-# safe. In here, we implement the safe port allocation by leveraging flock.
+# The following two methods are used to allocate the port source for various
+# types of test servers. Because some net-related tests can be run on shards at
+# same time, it's important to have a mechanism to allocate the port
+# process-safe. In here, we implement the safe port allocation by leveraging
+# flock.
def ResetTestServerPortAllocation():
- """Reset the port allocation to start from TEST_SERVER_PORT_FIRST.
+ """Resets the port allocation to start from TEST_SERVER_PORT_FIRST.
Returns:
Returns True if reset successes. Otherwise returns False.
@@ -39,7 +40,7 @@ def ResetTestServerPortAllocation():
def AllocateTestServerPort():
- """Allocate a port incrementally.
+ """Allocates a port incrementally.
Returns:
Returns a valid port which should be in between TEST_SERVER_PORT_FIRST and
@@ -90,10 +91,12 @@ def IsHostPortUsed(host_port):
Returns:
True if the port on host is already used, otherwise returns False.
"""
- port_info = '(127\.0\.0\.1)|(localhost)\:%d' % host_port
- # TODO(jnd): Find a better way to filter the port.
+ port_info = '(\*)|(127\.0\.0\.1)|(localhost):%d' % host_port
+ # TODO(jnd): Find a better way to filter the port. Note that connecting to the
+ # socket and closing it would leave it in the TIME_WAIT state. Setting
+ # SO_LINGER on it and then closing it makes the Python HTTP server crash.
re_port = re.compile(port_info, re.MULTILINE)
- if re_port.findall(cmd_helper.GetCmdOutput(['lsof', '-nPi:%d' % host_port])):
+ if re_port.search(cmd_helper.GetCmdOutput(['lsof', '-nPi:%d' % host_port])):
return True
return False
@@ -115,6 +118,11 @@ def IsDevicePortUsed(adb, device_port, state=''):
for single_connect in netstat_results:
# Column 3 is the local address which we want to check with.
connect_results = single_connect.split()
+ if connect_results[0] != 'tcp':
+ continue
+ if len(connect_results) < 6:
+ raise Exception('Unexpected format while parsing netstat line: ' +
+ single_connect)
is_state_match = connect_results[5] == state if state else True
if connect_results[3] == base_url and is_state_match:
return True
diff --git a/build/android/pylib/run_java_tests.py b/build/android/pylib/run_java_tests.py
index 62947fa..4e61e2d 100644
--- a/build/android/pylib/run_java_tests.py
+++ b/build/android/pylib/run_java_tests.py
@@ -268,8 +268,8 @@ class TestRunner(BaseTestRunner):
# We need to remember which ports the HTTP server is using, since the
# forwarder will stomp on them otherwise.
port_pairs.append(http_server_ports)
- self.forwarder = Forwarder(
- self.adb, port_pairs, self.tool, '127.0.0.1', self.build_type)
+ self.forwarder = Forwarder(self.adb, self.build_type)
+ self.forwarder.Run(port_pairs, self.tool, '127.0.0.1')
self.CopyTestFilesOnce()
self.flags.AddFlags(['--enable-test-intents'])
@@ -490,7 +490,7 @@ class TestSharder(BaseTestSharder):
"""Responsible for sharding the tests on the connected devices."""
def __init__(self, attached_devices, options, tests, apks):
- BaseTestSharder.__init__(self, attached_devices)
+ BaseTestSharder.__init__(self, attached_devices, options.build_type)
self.options = options
self.tests = tests
self.apks = apks
diff --git a/build/android/run_tests.py b/build/android/run_tests.py
index 67cfbbf..b4a8bf1 100755
--- a/build/android/run_tests.py
+++ b/build/android/run_tests.py
@@ -196,7 +196,7 @@ class TestSharder(BaseTestSharder):
test_arguments, timeout, rebaseline, performance_test,
cleanup_test_files, tool, log_dump_name, fast_and_loose,
build_type):
- BaseTestSharder.__init__(self, attached_devices)
+ BaseTestSharder.__init__(self, attached_devices, build_type)
self.test_suite = test_suite
self.test_suite_basename = os.path.basename(test_suite)
self.gtest_filter = gtest_filter or ''
diff --git a/tools/android/common/daemon.cc b/tools/android/common/daemon.cc
index 225c84e..48e9cd6 100644
--- a/tools/android/common/daemon.cc
+++ b/tools/android/common/daemon.cc
@@ -73,4 +73,3 @@ void SpawnDaemon(int exit_status) {
}
} // namespace tools
-
diff --git a/tools/android/forwarder2/command.cc b/tools/android/forwarder2/command.cc
index 9b9e401..c070ed9 100644
--- a/tools/android/forwarder2/command.cc
+++ b/tools/android/forwarder2/command.cc
@@ -10,6 +10,7 @@
#include <string.h>
#include "base/logging.h"
+#include "base/safe_strerror_posix.h"
#include "base/string_number_conversions.h"
#include "base/string_piece.h"
#include "tools/android/forwarder2/socket.h"
@@ -46,9 +47,14 @@ bool ReadCommand(Socket* socket,
// To make logging easier.
command_buffer[kCommandStringSize] = '\0';
- if (socket->ReadNumBytes(command_buffer, kCommandStringSize) !=
- kCommandStringSize) {
- LOG(ERROR) << "Not enough data received from the socket.";
+ int bytes_read = socket->ReadNumBytes(command_buffer, kCommandStringSize);
+ if (bytes_read != kCommandStringSize) {
+ if (bytes_read < 0)
+ LOG(ERROR) << "Read() error: " << safe_strerror(errno);
+ else if (!bytes_read)
+ LOG(ERROR) << "Read() error, endpoint was unexpectedly closed.";
+ else
+ LOG(ERROR) << "Read() error, not enough data received from the socket.";
return false;
}
diff --git a/tools/android/forwarder2/common.cc b/tools/android/forwarder2/common.cc
new file mode 100644
index 0000000..6accd30
--- /dev/null
+++ b/tools/android/forwarder2/common.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 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.
+
+#include "tools/android/forwarder2/common.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "base/eintr_wrapper.h"
+#include "base/logging.h"
+#include "base/safe_strerror_posix.h"
+
+namespace forwarder2 {
+
+void PError(const char* msg) {
+ LOG(ERROR) << msg << ": " << safe_strerror(errno);
+}
+
+void CloseFD(int fd) {
+ const int errno_copy = errno;
+ if (HANDLE_EINTR(close(fd)) < 0) {
+ PError("close");
+ errno = errno_copy;
+ }
+}
+
+} // namespace forwarder2
diff --git a/tools/android/forwarder2/common.h b/tools/android/forwarder2/common.h
new file mode 100644
index 0000000..3f546c6
--- /dev/null
+++ b/tools/android/forwarder2/common.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 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.
+
+// Common helper functions/classes used both in the host and device forwarder.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_COMMON_H_
+#define TOOLS_ANDROID_FORWARDER2_COMMON_H_
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/eintr_wrapper.h"
+#include "base/logging.h"
+
+// Preserving errno for Close() is important because the function is very often
+// used in cleanup code, after an error occurred, and it is very easy to pass an
+// invalid file descriptor to close() in this context, or more rarely, a
+// spurious signal might make close() return -1 + setting errno to EINTR,
+// masking the real reason for the original error. This leads to very unpleasant
+// debugging sessions.
+#define PRESERVE_ERRNO_HANDLE_EINTR(Func) \
+ do { \
+ int local_errno = errno; \
+ (void) HANDLE_EINTR(Func); \
+ errno = local_errno; \
+ } while (false);
+
+// Wrapper around RAW_LOG() which is signal-safe. The only purpose of this macro
+// is to avoid documenting uses of RawLog().
+#define SIGNAL_SAFE_LOG(Level, Msg) \
+ RAW_LOG(Level, Msg);
+
+namespace forwarder2 {
+
+// Note that the two following functions are not signal-safe.
+
+// Chromium logging-aware implementation of libc's perror().
+void PError(const char* msg);
+
+// Closes the provided file descriptor and logs an error if it failed.
+void CloseFD(int fd);
+
+// Helps build a formatted C-string allocated in a fixed-size array. This is
+// useful in signal handlers where base::StringPrintf() can't be used safely
+// (due to its use of LOG()).
+template <int BufferSize>
+class FixedSizeStringBuilder {
+ public:
+ FixedSizeStringBuilder() {
+ Reset();
+ }
+
+ const char* buffer() const { return buffer_; }
+
+ void Reset() {
+ buffer_[0] = 0;
+ write_ptr_ = buffer_;
+ }
+
+ // Returns the number of bytes appended to the underlying buffer or -1 if it
+ // failed.
+ int Append(const char* format, ...) PRINTF_FORMAT(/* + 1 for 'this' */ 2, 3) {
+ if (write_ptr_ >= buffer_ + BufferSize)
+ return -1;
+ va_list ap;
+ va_start(ap, format);
+ const int bytes_written = vsnprintf(
+ write_ptr_, BufferSize - (write_ptr_ - buffer_), format, ap);
+ va_end(ap);
+ if (bytes_written > 0)
+ write_ptr_ += bytes_written;
+ return bytes_written;
+ }
+
+ private:
+ char* write_ptr_;
+ char buffer_[BufferSize];
+
+ COMPILE_ASSERT(BufferSize >= 1, Size_of_buffer_must_be_at_least_one);
+ DISALLOW_COPY_AND_ASSIGN(FixedSizeStringBuilder);
+};
+
+} // namespace forwarder2
+
+#endif // TOOLS_ANDROID_FORWARDER2_COMMON_H_
diff --git a/tools/android/forwarder2/daemon.cc b/tools/android/forwarder2/daemon.cc
new file mode 100644
index 0000000..c056460
--- /dev/null
+++ b/tools/android/forwarder2/daemon.cc
@@ -0,0 +1,256 @@
+// Copyright (c) 2012 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.
+
+#include "tools/android/forwarder2/daemon.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/eintr_wrapper.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/safe_strerror_posix.h"
+#include "base/string_number_conversions.h"
+#include "base/stringprintf.h"
+#include "tools/android/forwarder2/common.h"
+
+namespace forwarder2 {
+namespace {
+
+const char kLogFilePath[] = "/tmp/host_forwarder_log";
+
+class FileDescriptorAutoCloser {
+ public:
+ explicit FileDescriptorAutoCloser(int fd) : fd_(fd) {
+ DCHECK(fd_ >= 0);
+ }
+
+ ~FileDescriptorAutoCloser() {
+ if (fd_ > -1)
+ CloseFD(fd_);
+ }
+
+ int Release() {
+ const int fd = fd_;
+ fd_ = -1;
+ return fd;
+ }
+
+ private:
+ int fd_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileDescriptorAutoCloser);
+};
+
+// Handles creation and destruction of the PID file.
+class PIDFile {
+ public:
+ static scoped_ptr<PIDFile> Create(const std::string& path) {
+ scoped_ptr<PIDFile> pid_file;
+ const int pid_file_fd = HANDLE_EINTR(
+ open(path.c_str(), O_CREAT | O_WRONLY, 0600));
+ if (pid_file_fd < 0) {
+ PError("open()");
+ return pid_file.Pass();
+ }
+ FileDescriptorAutoCloser fd_closer(pid_file_fd);
+ struct flock lock_info = {};
+ lock_info.l_type = F_WRLCK;
+ lock_info.l_whence = SEEK_CUR;
+ if (HANDLE_EINTR(fcntl(pid_file_fd, F_SETLK, &lock_info)) < 0) {
+ if (errno == EAGAIN || errno == EACCES) {
+ LOG(ERROR) << "Daemon already running (PID file already locked)";
+ return pid_file.Pass();
+ }
+ PError("lockf()");
+ return pid_file.Pass();
+ }
+ const std::string pid_string = base::StringPrintf("%d\n", getpid());
+ CHECK(HANDLE_EINTR(write(pid_file_fd, pid_string.c_str(),
+ pid_string.length())));
+ pid_file.reset(new PIDFile(fd_closer.Release(), path));
+ return pid_file.Pass();
+ }
+
+ ~PIDFile() {
+ CloseFD(fd_); // This also releases the lock.
+ if (remove(path_.c_str()) < 0)
+ PError("remove");
+ }
+
+ private:
+ PIDFile(int fd, const std::string& path) : fd_(fd), path_(path) {
+ DCHECK(fd_ >= 0);
+ }
+
+ const int fd_;
+ const std::string path_;
+
+ DISALLOW_COPY_AND_ASSIGN(PIDFile);
+};
+
+// Takes ownership of |data|.
+void ReleaseDaemonResourcesAtExit(void* data) {
+ DCHECK(data);
+ delete reinterpret_cast<PIDFile*>(data);
+}
+
+void InitLogging(const char* log_file) {
+ CHECK(
+ logging::InitLogging(
+ log_file,
+ logging::LOG_ONLY_TO_FILE,
+ logging::DONT_LOCK_LOG_FILE,
+ logging::APPEND_TO_OLD_LOG_FILE,
+ logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS));
+}
+
+void SigChildHandler(int signal_number) {
+ DCHECK_EQ(signal_number, SIGCHLD);
+ // The daemon should not terminate while its parent is still running.
+ int status;
+ pid_t child_pid = waitpid(-1 /* any child */, &status, WNOHANG);
+ if (child_pid < 0) {
+ PError("waitpid");
+ return;
+ }
+ if (child_pid == 0)
+ return;
+ // Avoid using StringAppendF() since it's unsafe in a signal handler due to
+ // its use of LOG().
+ FixedSizeStringBuilder<256> string_builder;
+ string_builder.Append("Daemon (pid=%d) died unexpectedly with ", child_pid);
+ if (WIFEXITED(status))
+ string_builder.Append("status %d.", WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ string_builder.Append("signal %d.", WTERMSIG(status));
+ else
+ string_builder.Append("unknown reason.");
+ SIGNAL_SAFE_LOG(ERROR, string_builder.buffer());
+}
+
+// Note that 0 is written to |lock_owner_pid| in case the file is not locked.
+bool GetFileLockOwnerPid(int fd, pid_t* lock_owner_pid) {
+ struct flock lock_info = {};
+ lock_info.l_type = F_WRLCK;
+ lock_info.l_whence = SEEK_CUR;
+ const int ret = HANDLE_EINTR(fcntl(fd, F_GETLK, &lock_info));
+ if (ret < 0) {
+ if (errno == EBADF) {
+ // Assume that the provided file descriptor corresponding to the PID file
+ // was valid until the daemon removed this file.
+ *lock_owner_pid = 0;
+ return true;
+ }
+ PError("fcntl");
+ return false;
+ }
+ if (lock_info.l_type == F_UNLCK) {
+ *lock_owner_pid = 0;
+ return true;
+ }
+ CHECK_EQ(F_WRLCK /* exclusive lock */, lock_info.l_type);
+ *lock_owner_pid = lock_info.l_pid;
+ return true;
+}
+
+} // namespace
+
+Daemon::Daemon(const std::string& pid_file_path)
+ : pid_file_path_(pid_file_path) {
+}
+
+bool Daemon::Spawn(bool* is_daemon) {
+ switch (fork()) {
+ case -1:
+ *is_daemon = false;
+ PError("fork()");
+ return false;
+ case 0: { // Child.
+ *is_daemon = true;
+ scoped_ptr<PIDFile> pid_file = PIDFile::Create(pid_file_path_);
+ if (!pid_file)
+ return false;
+ base::AtExitManager::RegisterCallback(
+ &ReleaseDaemonResourcesAtExit, pid_file.release());
+ if (setsid() < 0) { // Detach the child process from its parent.
+ PError("setsid");
+ return false;
+ }
+ CloseFD(STDOUT_FILENO);
+ CloseFD(STDERR_FILENO);
+ InitLogging(kLogFilePath);
+ break;
+ }
+ default: // Parent.
+ *is_daemon = false;
+ signal(SIGCHLD, SigChildHandler);
+ }
+ return true;
+}
+
+bool Daemon::Kill() {
+ int pid_file_fd = HANDLE_EINTR(open(pid_file_path_.c_str(), O_WRONLY));
+ if (pid_file_fd < 0) {
+ if (errno == ENOENT)
+ return true;
+ LOG(ERROR) << "Could not open " << pid_file_path_ << " in write mode: "
+ << safe_strerror(errno);
+ return false;
+ }
+ const FileDescriptorAutoCloser fd_closer(pid_file_fd);
+ pid_t lock_owner_pid;
+ if (!GetFileLockOwnerPid(pid_file_fd, &lock_owner_pid))
+ return false;
+ if (lock_owner_pid == 0)
+ // No daemon running.
+ return true;
+ if (kill(lock_owner_pid, SIGTERM) < 0) {
+ if (errno == ESRCH /* invalid PID */)
+ // The daemon exited for some reason (e.g. kill by a process other than
+ // us) right before the call to kill() above.
+ return true;
+ PError("kill");
+ return false;
+ }
+ // Wait until the daemon exits. Rely on the fact that the daemon releases the
+ // lock on the PID file when it exits.
+ // TODO(pliard): Consider using a mutex + condition in shared memory to avoid
+ // polling.
+ const int kTries = 20;
+ const int kIdleTimeMS = 50;
+ for (int i = 0; i < kTries; ++i) {
+ pid_t current_lock_owner_pid;
+ if (!GetFileLockOwnerPid(pid_file_fd, &current_lock_owner_pid))
+ return false;
+ if (current_lock_owner_pid == 0)
+ // The daemon released the PID file's lock.
+ return true;
+ // Since we are polling we might not see the 'daemon exited' event if
+ // another daemon was spawned during our idle period.
+ if (current_lock_owner_pid != lock_owner_pid) {
+ LOG(WARNING) << "Daemon (pid=" << lock_owner_pid
+ << ") was successfully killed but a new daemon (pid="
+ << current_lock_owner_pid << ") seems to be running now.";
+ return true;
+ }
+ usleep(kIdleTimeMS * 1000);
+ }
+ LOG(ERROR) << "Timed out while killing daemon. "
+ "It might still be tearing down.";
+ return false;
+}
+
+} // namespace forwarder2
diff --git a/tools/android/forwarder2/daemon.h b/tools/android/forwarder2/daemon.h
new file mode 100644
index 0000000..f3654e0
--- /dev/null
+++ b/tools/android/forwarder2/daemon.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 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.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_DAEMON_H_
+#define TOOLS_ANDROID_FORWARDER2_DAEMON_H_
+
+#include <string>
+
+#include "base/at_exit.h"
+#include "base/basictypes.h"
+
+namespace forwarder2 {
+
+class Daemon {
+ public:
+ // |pid_file_path| is the file path to which the daemon's PID will be written.
+ // Note that a lock on the file is also acquired to guarantee that a single
+ // instance of daemon is running.
+ explicit Daemon(const std::string& pid_file_path);
+
+ // Returns whether the daemon was successfully spawned. Use |is_daemon| to
+ // distinguish the parent from the child (daemon) process.
+ bool Spawn(bool* is_daemon);
+
+ // Kills the daemon and blocks until it exited.
+ bool Kill();
+
+ private:
+ const std::string pid_file_path_;
+ base::AtExitManager at_exit_manager;
+
+ DISALLOW_COPY_AND_ASSIGN(Daemon);
+};
+
+} // namespace forwarder2
+
+#endif // TOOLS_ANDROID_FORWARDER2_DAEMON_H_
diff --git a/tools/android/forwarder2/device_controller.cc b/tools/android/forwarder2/device_controller.cc
index ff4470a..9ab0b48 100644
--- a/tools/android/forwarder2/device_controller.cc
+++ b/tools/android/forwarder2/device_controller.cc
@@ -52,6 +52,7 @@ bool DeviceController::Init(const std::string& adb_unix_socket) {
<< adb_unix_socket << ": " << safe_strerror(errno);
return false;
}
+ LOG(INFO) << "Listening on Unix Domain Socket " << adb_unix_socket;
return true;
}
@@ -60,8 +61,10 @@ void DeviceController::Start() {
CleanUpDeadListeners();
scoped_ptr<Socket> socket(new Socket);
if (!kickstart_adb_socket_.Accept(socket.get())) {
- LOG(ERROR) << "Could not Accept DeviceController socket: "
- << safe_strerror(errno);
+ if (!kickstart_adb_socket_.exited()) {
+ LOG(ERROR) << "Could not Accept DeviceController socket: "
+ << safe_strerror(errno);
+ }
break;
}
// So that |socket| doesn't block on read if it has notifications.
@@ -97,7 +100,7 @@ void DeviceController::Start() {
const int listener_port = new_listener->listener_port();
// |new_listener| is now owned by listeners_ map.
listeners_.AddWithID(new_listener.release(), listener_port);
- printf("Forwarding device port %d to host.\n", listener_port);
+ LOG(INFO) << "Forwarding device port " << listener_port << " to host.";
break;
}
case command::DATA_CONNECTION:
@@ -121,8 +124,6 @@ void DeviceController::Start() {
LOG(ERROR) << "Invalid command received. Port: " << port
<< " Command: " << command;
socket->Close();
- continue;
- break;
}
}
KillAllListeners();
diff --git a/tools/android/forwarder2/device_listener.cc b/tools/android/forwarder2/device_listener.cc
index fdabc70..d67151d 100644
--- a/tools/android/forwarder2/device_listener.cc
+++ b/tools/android/forwarder2/device_listener.cc
@@ -106,6 +106,10 @@ void DeviceListener::RunInternal() {
while (!must_exit_) {
scoped_ptr<Socket> device_data_socket(new Socket);
if (!listener_socket_.Accept(device_data_socket.get())) {
+ if (listener_socket_.exited()) {
+ LOG(INFO) << "Received exit notification, stopped accepting clients.";
+ break;
+ }
LOG(WARNING) << "Could not Accept in ListenerSocket.";
SendCommand(command::ACCEPT_ERROR,
listener_port_,
@@ -160,7 +164,8 @@ bool DeviceListener::BindListenerSocket() {
adb_control_socket_.get());
is_alive_ = true;
} else {
- LOG(ERROR) << "Device could not bind and listen to local port.";
+ LOG(ERROR) << "Device could not bind and listen to local port "
+ << listener_port_;
SendCommand(command::BIND_ERROR,
listener_port_,
adb_control_socket_.get());
diff --git a/tools/android/forwarder2/forwarder.gyp b/tools/android/forwarder2/forwarder.gyp
index f636ad4..205975f 100644
--- a/tools/android/forwarder2/forwarder.gyp
+++ b/tools/android/forwarder2/forwarder.gyp
@@ -38,6 +38,7 @@
],
'sources': [
'command.cc',
+ 'common.cc',
'device_controller.cc',
'device_forwarder_main.cc',
'device_listener.cc',
@@ -60,6 +61,8 @@
],
'sources': [
'command.cc',
+ 'common.cc',
+ 'daemon.cc',
'forwarder.cc',
'host_controller.cc',
'host_forwarder_main.cc',
diff --git a/tools/android/forwarder2/host_controller.cc b/tools/android/forwarder2/host_controller.cc
index 98e487f..b95d986 100644
--- a/tools/android/forwarder2/host_controller.cc
+++ b/tools/android/forwarder2/host_controller.cc
@@ -35,7 +35,7 @@ void HostController::StartForwarder(
scoped_ptr<Socket> host_server_data_socket) {
scoped_ptr<Socket> adb_data_socket(new Socket);
if (!adb_data_socket->ConnectTcp("", adb_port_)) {
- LOG(ERROR) << "Could not Connect AdbDataSocket on port: "
+ LOG(ERROR) << "Could not connect AdbDataSocket on port: "
<< adb_port_;
return;
}
@@ -63,7 +63,7 @@ void HostController::StartForwarder(
bool HostController::Connect() {
if (!adb_control_socket_.ConnectTcp("", adb_port_)) {
- LOG(ERROR) << "Could not Connect HostController socket on port: "
+ LOG(ERROR) << "Could not connect HostController socket on port: "
<< adb_port_;
return false;
}
@@ -91,6 +91,11 @@ void HostController::Run() {
while (true) {
if (!ReceivedCommand(command::ACCEPT_SUCCESS,
&adb_control_socket_)) {
+ // TODO(pliard): This can also happen if device_forwarder was
+ // intentionally killed before host_forwarder. In that case,
+ // device_forwarder should send a notification to the host. Currently the
+ // error message below is always emitted to the log file although this is
+ // not necessarily an error.
LOG(ERROR) << "Device socket error on accepting using port "
<< device_port_;
break;
diff --git a/tools/android/forwarder2/host_forwarder_main.cc b/tools/android/forwarder2/host_forwarder_main.cc
index 6e54189..dff0c8f 100644
--- a/tools/android/forwarder2/host_forwarder_main.cc
+++ b/tools/android/forwarder2/host_forwarder_main.cc
@@ -6,32 +6,49 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
+#include <unistd.h>
#include <vector>
#include <string>
#include "base/command_line.h"
+#include "base/eintr_wrapper.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_vector.h"
+#include "base/safe_strerror_posix.h"
#include "base/string_number_conversions.h"
#include "base/string_piece.h"
#include "base/string_split.h"
+#include "base/string_util.h"
#include "base/stringprintf.h"
-#include "tools/android/common/daemon.h"
+#include "tools/android/forwarder2/common.h"
+#include "tools/android/forwarder2/daemon.h"
#include "tools/android/forwarder2/host_controller.h"
#include "tools/android/forwarder2/pipe_notifier.h"
+#include "tools/android/forwarder2/socket.h"
using base::StringToInt;
-using forwarder2::HostController;
+namespace forwarder2 {
namespace {
-const int kDefaultAdbPort = 3000;
+const char kPIDFilePath[] = "/tmp/host_forwarder_pid";
+const char kCommandSocketPath[] = "host_forwarder_command_socket";
+const char kWelcomeMessage[] = "forwarder2";
+const int kBufSize = 256;
// Need to be global to be able to accessed from the signal handler.
-forwarder2::PipeNotifier* g_notifier;
+PipeNotifier* g_notifier;
-void KillHandler(int /* unused */) {
+void KillHandler(int signal_number) {
+ if (signal_number != SIGTERM && signal_number != SIGINT) {
+ char buf[kBufSize];
+ snprintf(buf, sizeof(buf), "Ignoring unexpected signal %d.", signal_number);
+ SIGNAL_SAFE_LOG(WARNING, buf);
+ return;
+ }
static int s_kill_handler_count = 0;
CHECK(g_notifier);
// If for some reason the forwarder get stuck in any socket waiting forever,
@@ -42,110 +59,224 @@ void KillHandler(int /* unused */) {
exit(1);
}
-// Format of arg: <Device port>[:<Forward to port>:<Forward to address>]
-bool ParseForwardArg(const std::string& arg,
- int* device_port,
- std::string* forward_to_host,
- int* forward_to_port) {
- std::vector<std::string> arg_pieces;
- base::SplitString(arg, ':', &arg_pieces);
- if (arg_pieces.size() == 0 || !StringToInt(arg_pieces[0], device_port))
+enum {
+ kConnectSingleTry = 1,
+ kConnectNoIdleTime = 0,
+};
+
+scoped_ptr<Socket> ConnectToDaemon(int tries_count, int idle_time_msec) {
+ for (int i = 0; i < tries_count; ++i) {
+ scoped_ptr<Socket> socket(new Socket());
+ if (!socket->ConnectUnix(kCommandSocketPath, true)) {
+ if (idle_time_msec)
+ usleep(idle_time_msec * 1000);
+ continue;
+ }
+ char buf[sizeof(kWelcomeMessage)];
+ memset(buf, 0, sizeof(buf));
+ if (socket->Read(buf, sizeof(buf)) < 0) {
+ perror("read");
+ continue;
+ }
+ if (strcmp(buf, kWelcomeMessage)) {
+ LOG(ERROR) << "Unexpected message read from daemon: " << buf;
+ break;
+ }
+ return socket.Pass();
+ }
+ return scoped_ptr<Socket>(NULL);
+}
+
+// Format of |command|:
+// <ADB port>:<Device port>[:<Forward to port>:<Forward to address>].
+bool ParseForwardCommand(const std::string& command,
+ int* adb_port,
+ int* device_port,
+ std::string* forward_to_host,
+ int* forward_to_port) {
+ std::vector<std::string> command_pieces;
+ base::SplitString(command, ':', &command_pieces);
+
+ if (command_pieces.size() < 2 ||
+ !StringToInt(command_pieces[0], adb_port) ||
+ !StringToInt(command_pieces[1], device_port))
return false;
- if (arg_pieces.size() > 1) {
- if (!StringToInt(arg_pieces[1], forward_to_port))
+ if (command_pieces.size() > 2) {
+ if (!StringToInt(command_pieces[2], forward_to_port))
return false;
- if (arg_pieces.size() > 2)
- *forward_to_host = arg_pieces[2];
+ if (command_pieces.size() > 3)
+ *forward_to_host = command_pieces[3];
} else {
*forward_to_port = *device_port;
}
return true;
}
-} // namespace
+bool IsForwardCommandValid(const std::string& command) {
+ int adb_port, device_port, forward_to_port;
+ std::string forward_to_host;
+ std::vector<std::string> command_pieces;
+ return ParseForwardCommand(
+ command, &adb_port, &device_port, &forward_to_host, &forward_to_port);
+}
-int main(int argc, char** argv) {
- printf("Host forwarder to handle incoming connections from Android.\n");
- printf("Like 'adb forward' but in the reverse direction\n");
-
- CommandLine command_line(argc, argv);
- bool show_help = tools::HasHelpSwitch(command_line);
- std::string adb_port_str = command_line.GetSwitchValueASCII("adb_port");
- int adb_port = kDefaultAdbPort;
- if (!adb_port_str.empty() && !StringToInt(adb_port_str, &adb_port)) {
- printf("Could not parse adb port number: %s\n", adb_port_str.c_str());
- show_help = true;
- }
- if (adb_port <= 0) {
- printf("Invalid adb port number: %s. Adb port must be a "
- "postivie integer.\n", adb_port_str.c_str());
- show_help = true;
- }
- CommandLine::StringVector forward_args = command_line.GetArgs();
- if (show_help || forward_args.empty()) {
- tools::ShowHelp(
- argv[0],
- "[--adb_port=<adb port>] "
- "<Device port>[:<Forward to port>:<Forward to address>] ...",
- base::StringPrintf(
- " <adb port> is the TCP port Adb is configured to forward to."
- " Default is %d\n"
- " <Forward to port> default is <Device port>\n"
- " <Forward to address> default is 127.0.0.1.",
- kDefaultAdbPort).c_str());
- return 1;
+bool DaemonHandler() {
+ LOG(INFO) << "Starting host process daemon (pid=" << getpid() << ")";
+ DCHECK(!g_notifier);
+ g_notifier = new PipeNotifier();
+
+ const int notifier_fd = g_notifier->receiver_fd();
+ Socket command_socket;
+ if (!command_socket.BindUnix(kCommandSocketPath, true)) {
+ LOG(ERROR) << "Could not bind Unix Domain Socket";
+ return false;
}
+ command_socket.set_exit_notifier_fd(notifier_fd);
+
+ signal(SIGTERM, KillHandler);
+ signal(SIGINT, KillHandler);
- g_notifier = new forwarder2::PipeNotifier();
ScopedVector<HostController> controllers;
int failed_count = 0;
- for (size_t i = 0; i < forward_args.size(); ++i) {
+
+ for (;;) {
+ Socket client_socket;
+ if (!command_socket.Accept(&client_socket)) {
+ if (command_socket.exited())
+ return true;
+ PError("Accept()");
+ return false;
+ }
+ if (!client_socket.Write(kWelcomeMessage, sizeof(kWelcomeMessage))) {
+ PError("Write()");
+ continue;
+ }
+ char buf[kBufSize];
+ const int bytes_read = client_socket.Read(buf, sizeof(buf));
+ if (bytes_read <= 0) {
+ if (client_socket.exited())
+ break;
+ PError("Read()");
+ ++failed_count;
+ }
+ const std::string command(buf, bytes_read);
+ int adb_port = 0;
int device_port = 0;
std::string forward_to_host;
int forward_to_port = 0;
- if (ParseForwardArg(forward_args[i],
- &device_port,
- &forward_to_host,
- &forward_to_port)) {
- scoped_ptr<HostController> host_controller(
- new HostController(device_port,
- forward_to_host,
- forward_to_port,
- adb_port,
- g_notifier->receiver_fd()));
- if (!host_controller->Connect())
- continue;
- host_controller->Start();
- // Get the current allocated port.
- device_port = host_controller->device_port();
- printf("Forwarding device port %d to host %d:%s\n",
- device_port, forward_to_port, forward_to_host.c_str());
-
- controllers.push_back(host_controller.release());
- } else {
- printf("Couldn't start forwarder server for port spec: %s\n",
- forward_args[i].c_str());
+ const bool succeeded = ParseForwardCommand(
+ command, &adb_port, &device_port, &forward_to_host, &forward_to_port);
+ if (!succeeded) {
+ ++failed_count;
+ client_socket.WriteString(
+ base::StringPrintf("ERROR: Could not parse forward command '%s'",
+ command.c_str()));
+ continue;
+ }
+ scoped_ptr<HostController> host_controller(
+ new HostController(device_port, forward_to_host, forward_to_port,
+ adb_port, notifier_fd));
+ if (!host_controller->Connect()) {
++failed_count;
+ client_socket.WriteString("ERROR: Connection to device failed.");
+ continue;
}
+ // Get the current allocated port.
+ device_port = host_controller->device_port();
+ LOG(INFO) << "Forwarding device port " << device_port << " to host "
+ << forward_to_host << ":" << forward_to_port;
+ if (!client_socket.WriteString(
+ base::StringPrintf("%d:%d", device_port, forward_to_port))) {
+ ++failed_count;
+ continue;
+ }
+ host_controller->Start();
+ controllers.push_back(host_controller.release());
}
-
- // Signal handler must be installed after the for loop above where we start
- // the host_controllers and push_back into the vector. Otherwise a race
- // condition may occur.
- signal(SIGTERM, KillHandler);
- signal(SIGINT, KillHandler);
+ for (int i = 0; i < controllers.size(); ++i)
+ controllers[i]->Join();
if (controllers.size() == 0) {
- printf("No forwarder servers could be started. Exiting.\n");
- return failed_count;
+ LOG(ERROR) << "No forwarder servers could be started. Exiting.";
+ return false;
}
+ return true;
+}
- // TODO(felipeg): We should check if the controllers are really alive before
- // printing Ready.
- printf("Host Forwarder Ready.\n");
- for (int i = 0; i < controllers.size(); ++i)
- controllers[i]->Join();
+void PrintUsage(const char* program_name) {
+ LOG(ERROR) << program_name << " adb_port:from_port:to_port:to_host\n"
+ "<adb port> is the TCP port Adb is configured to forward to.";
+}
+
+int RunHostForwarder(int argc, char** argv) {
+ if (!CommandLine::Init(argc, argv)) {
+ LOG(ERROR) << "Could not initialize command line";
+ return 1;
+ }
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ std::string command;
+ int adb_port = 0;
+ if (argc != 2) {
+ PrintUsage(argv[0]);
+ return 1;
+ }
+ if (!strcmp(argv[1], "kill-server")) {
+ command = "kill-server";
+ } else {
+ command = "forward";
+ if (!IsForwardCommandValid(argv[1])) {
+ PrintUsage(argv[0]);
+ return 1;
+ }
+ }
+
+ Daemon daemon(kPIDFilePath);
+
+ if (command == "kill-server")
+ return !daemon.Kill();
+
+ bool is_daemon = false;
+ scoped_ptr<Socket> daemon_socket = ConnectToDaemon(
+ kConnectSingleTry, kConnectNoIdleTime);
+ if (!daemon_socket) {
+ if (!daemon.Spawn(&is_daemon))
+ return 1;
+ }
+
+ if (is_daemon)
+ return !DaemonHandler();
+
+ if (!daemon_socket) {
+ const int kTries = 10;
+ const int kIdleTimeMsec = 10;
+ daemon_socket = ConnectToDaemon(kTries, kIdleTimeMsec);
+ if (!daemon_socket) {
+ LOG(ERROR) << "Could not connect to daemon.";
+ return 1;
+ }
+ }
+ // Send the forward command to the daemon.
+ CHECK(daemon_socket->Write(argv[1], strlen(argv[1])));
+ char buf[kBufSize];
+ const int bytes_read = daemon_socket->Read(
+ buf, sizeof(buf) - 1 /* leave space for null terminator */);
+ CHECK_GT(bytes_read, 0);
+ DCHECK(bytes_read < sizeof(buf));
+ buf[bytes_read] = 0;
+ base::StringPiece msg(buf, bytes_read);
+ if (msg.starts_with("ERROR")) {
+ LOG(ERROR) << msg;
+ return 1;
+ }
+ printf("%s\n", buf);
return 0;
}
+
+} // namespace
+} // namespace forwarder2
+
+int main(int argc, char** argv) {
+ return forwarder2::RunHostForwarder(argc, argv);
+}
diff --git a/tools/android/forwarder2/socket.cc b/tools/android/forwarder2/socket.cc
index 35553bb..397c096 100644
--- a/tools/android/forwarder2/socket.cc
+++ b/tools/android/forwarder2/socket.cc
@@ -17,21 +17,7 @@
#include "base/logging.h"
#include "base/safe_strerror_posix.h"
#include "tools/android/common/net.h"
-
-// This is used in Close and Shutdown.
-// Preserving errno for Close() is important because the function is very often
-// used in cleanup code, after an error occurred, and it is very easy to pass an
-// invalid file descriptor to close() in this context, or more rarely, a
-// spurious signal might make close() return -1 + setting errno to EINTR,
-// masking the real reason for the original error. This leads to very unpleasant
-// debugging sessions.
-#define PRESERVE_ERRNO_HANDLE_EINTR(Func) \
- do { \
- int local_errno = errno; \
- (void) HANDLE_EINTR(Func); \
- errno = local_errno; \
- } while (false);
-
+#include "tools/android/forwarder2/common.h"
namespace {
const int kNoTimeout = -1;
@@ -88,12 +74,13 @@ Socket::Socket()
abstract_(false),
addr_ptr_(reinterpret_cast<sockaddr*>(&addr_.addr4)),
addr_len_(sizeof(sockaddr)),
- exit_notifier_fd_(-1) {
+ exit_notifier_fd_(-1),
+ exited_(false) {
memset(&addr_, 0, sizeof(addr_));
}
Socket::~Socket() {
- CHECK(IsClosed());
+ Close();
}
void Socket::Shutdown() {
@@ -104,7 +91,7 @@ void Socket::Shutdown() {
void Socket::Close() {
if (!IsClosed()) {
- PRESERVE_ERRNO_HANDLE_EINTR(close(socket_));
+ CloseFD(socket_);
socket_ = -1;
}
}
@@ -242,7 +229,20 @@ bool Socket::Connect() {
PRESERVE_ERRNO_HANDLE_EINTR(fcntl(socket_, F_SETFL, kFlags));
return false;
}
- // Disable non-block since our code assumes blocking semantics.
+ int socket_errno;
+ socklen_t opt_len = sizeof(socket_errno);
+ if (!getsockopt(socket_, SOL_SOCKET, SO_ERROR, &socket_errno, &opt_len) < 0) {
+ LOG(ERROR) << "getsockopt(): " << safe_strerror(errno);
+ SetSocketError();
+ PRESERVE_ERRNO_HANDLE_EINTR(fcntl(socket_, F_SETFL, kFlags));
+ return false;
+ }
+ if (socket_errno != 0) {
+ LOG(ERROR) << "Could not connect to host: " << safe_strerror(socket_errno);
+ SetSocketError();
+ PRESERVE_ERRNO_HANDLE_EINTR(fcntl(socket_, F_SETFL, kFlags));
+ return false;
+ }
fcntl(socket_, F_SETFL, kFlags);
return true;
}
@@ -297,11 +297,11 @@ bool Socket::AddFdToSet(fd_set* fds) const {
return true;
}
-int Socket::ReadNumBytes(char* buffer, size_t num_bytes) {
+int Socket::ReadNumBytes(void* buffer, size_t num_bytes) {
int bytes_read = 0;
int ret = 1;
while (bytes_read < num_bytes && ret > 0) {
- ret = Read(buffer + bytes_read, num_bytes - bytes_read);
+ ret = Read(static_cast<char*>(buffer) + bytes_read, num_bytes - bytes_read);
if (ret >= 0)
bytes_read += ret;
}
@@ -315,7 +315,7 @@ void Socket::SetSocketError() {
Close();
}
-int Socket::Read(char* buffer, size_t buffer_size) {
+int Socket::Read(void* buffer, size_t buffer_size) {
if (!WaitForEvent(READ, kNoTimeout)) {
SetSocketError();
return 0;
@@ -326,7 +326,7 @@ int Socket::Read(char* buffer, size_t buffer_size) {
return ret;
}
-int Socket::Write(const char* buffer, size_t count) {
+int Socket::Write(const void* buffer, size_t count) {
int ret = HANDLE_EINTR(send(socket_, buffer, count, MSG_NOSIGNAL));
if (ret < 0)
SetSocketError();
@@ -337,18 +337,19 @@ int Socket::WriteString(const std::string& buffer) {
return WriteNumBytes(buffer.c_str(), buffer.size());
}
-int Socket::WriteNumBytes(const char* buffer, size_t num_bytes) {
+int Socket::WriteNumBytes(const void* buffer, size_t num_bytes) {
int bytes_written = 0;
int ret = 1;
while (bytes_written < num_bytes && ret > 0) {
- ret = Write(buffer + bytes_written, num_bytes - bytes_written);
+ ret = Write(static_cast<const char*>(buffer) + bytes_written,
+ num_bytes - bytes_written);
if (ret >= 0)
bytes_written += ret;
}
return bytes_written;
}
-bool Socket::WaitForEvent(EventType type, int timeout_secs) const {
+bool Socket::WaitForEvent(EventType type, int timeout_secs) {
if (exit_notifier_fd_ == -1 || socket_ == -1)
return true;
const int nfds = std::max(socket_, exit_notifier_fd_) + 1;
@@ -371,7 +372,11 @@ bool Socket::WaitForEvent(EventType type, int timeout_secs) const {
}
if (HANDLE_EINTR(select(nfds, &read_fds, &write_fds, NULL, tv_ptr)) <= 0)
return false;
- return !FD_ISSET(exit_notifier_fd_, &read_fds);
+ if (FD_ISSET(exit_notifier_fd_, &read_fds)) {
+ exited_ = true;
+ return false;
+ }
+ return true;
}
// static
diff --git a/tools/android/forwarder2/socket.h b/tools/android/forwarder2/socket.h
index 15a04b0..7014069 100644
--- a/tools/android/forwarder2/socket.h
+++ b/tools/android/forwarder2/socket.h
@@ -46,23 +46,24 @@ class Socket {
// Just a wrapper around unix read() function.
// Reads up to buffer_size, but may read less then buffer_size.
// Returns the number of bytes read.
- int Read(char* buffer, size_t buffer_size);
+ int Read(void* buffer, size_t buffer_size);
// Same as Read(), just a wrapper around write().
- int Write(const char* buffer, size_t count);
+ int Write(const void* buffer, size_t count);
// Calls Read() multiple times until num_bytes is written to the provided
// buffer. No bounds checking is performed.
// Returns number of bytes read, which can be different from num_bytes in case
// of errror.
- int ReadNumBytes(char* buffer, size_t num_bytes);
+ int ReadNumBytes(void* buffer, size_t num_bytes);
// Calls Write() multiple times until num_bytes is written. No bounds checking
// is performed. Returns number of bytes written, which can be different from
// num_bytes in case of errror.
- int WriteNumBytes(const char* buffer, size_t num_bytes);
+ int WriteNumBytes(const void* buffer, size_t num_bytes);
- // Calls WriteNumBytes for the given std::string.
+ // Calls WriteNumBytes for the given std::string. Note that the null
+ // terminator is not written to the socket.
int WriteString(const std::string& buffer);
bool has_error() const { return socket_error_; }
@@ -77,6 +78,10 @@ class Socket {
// anymore.
void reset_exit_notifier_fd() { exit_notifier_fd_ = -1; }
+ // Returns whether Accept() or Connect() was interrupted because the socket
+ // received an exit notification.
+ bool exited() const { return exited_; }
+
static int GetHighestFileDescriptor(const Socket& s1, const Socket& s2);
private:
@@ -104,12 +109,9 @@ class Socket {
WRITE
};
- // Waits until either the Socket or the |exit_notifier_fd_| has received a
- // read event (accept or read). Returns false iff an exit notification was
- // received. If |read| is false, it waits until Socket is ready to write,
- // instead. If |timeout_secs| is a non-negative value, it sets the timeout,
- // for the select operation.
- bool WaitForEvent(EventType type, int timeout_secs) const;
+ // Waits until either the Socket or the |exit_notifier_fd_| has received an
+ // event.
+ bool WaitForEvent(EventType type, int timeout_secs);
int socket_;
int port_;
@@ -133,6 +135,8 @@ class Socket {
// and Accept.
int exit_notifier_fd_;
+ bool exited_;
+
DISALLOW_COPY_AND_ASSIGN(Socket);
};