diff options
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, ¤t_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); }; |