summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tools/android/OWNERS6
-rw-r--r--tools/android/common/adb_connection.cc105
-rw-r--r--tools/android/common/adb_connection.h19
-rw-r--r--tools/android/common/common.gyp27
-rw-r--r--tools/android/common/daemon.cc70
-rw-r--r--tools/android/common/daemon.h31
-rw-r--r--tools/android/common/net.cc25
-rw-r--r--tools/android/common/net.h21
-rw-r--r--tools/android/forwarder/forwarder.cc405
-rw-r--r--tools/android/forwarder/forwarder.gyp47
10 files changed, 756 insertions, 0 deletions
diff --git a/tools/android/OWNERS b/tools/android/OWNERS
new file mode 100644
index 0000000..26eb81a
--- /dev/null
+++ b/tools/android/OWNERS
@@ -0,0 +1,6 @@
+set noparent
+jnd@chromium.org
+jrg@chromium.org
+tonyg@chromium.org
+wangxianzhu@chromium.org
+yfriedman@chromium.org
diff --git a/tools/android/common/adb_connection.cc b/tools/android/common/adb_connection.cc
new file mode 100644
index 0000000..c542d16
--- /dev/null
+++ b/tools/android/common/adb_connection.cc
@@ -0,0 +1,105 @@
+// 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/common/adb_connection.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/eintr_wrapper.h"
+#include "base/logging.h"
+#include "tools/android/common/net.h"
+
+namespace tools {
+
+int ConnectAdbHostSocket(const char* forward_to) {
+ // ADB port forward request format: HHHHtcp:port:address.
+ // HHHH is the hexidecimal length of the "tcp:port:address" part.
+ const size_t kBufferMaxLength = 30;
+ const size_t kLengthOfLength = 4;
+ const size_t kAddressMaxLength = kBufferMaxLength - kLengthOfLength;
+
+ const char kAddressPrefix[] = { 't', 'c', 'p', ':' };
+ size_t address_length = arraysize(kAddressPrefix) + strlen(forward_to);
+ if (address_length > kBufferMaxLength - kLengthOfLength) {
+ LOG(ERROR) << "Forward to address is too long: " << forward_to;
+ return -1;
+ }
+
+ char request[kBufferMaxLength];
+ memcpy(request + kLengthOfLength, kAddressPrefix, arraysize(kAddressPrefix));
+ memcpy(request + kLengthOfLength + arraysize(kAddressPrefix),
+ forward_to, strlen(forward_to));
+
+ char length_buffer[kLengthOfLength + 1];
+ snprintf(length_buffer, arraysize(length_buffer), "%04X",
+ static_cast<int>(address_length));
+ memcpy(request, length_buffer, kLengthOfLength);
+
+ int host_socket = socket(AF_INET, SOCK_STREAM, 0);
+ if (host_socket < 0) {
+ LOG(ERROR) << "Failed to create adb socket: " << strerror(errno);
+ return -1;
+ }
+
+ DisableNagle(host_socket);
+
+ const int kAdbPort = 5037;
+ sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr.sin_port = htons(kAdbPort);
+ if (HANDLE_EINTR(connect(host_socket, reinterpret_cast<sockaddr*>(&addr),
+ sizeof(addr))) < 0) {
+ LOG(ERROR) << "Failed to connect adb socket: " << strerror(errno);
+ HANDLE_EINTR(close(host_socket));
+ return -1;
+ }
+
+ size_t bytes_remaining = address_length + kLengthOfLength;
+ size_t bytes_sent = 0;
+ while (bytes_remaining > 0) {
+ int ret = HANDLE_EINTR(send(host_socket, request + bytes_sent,
+ bytes_remaining, 0));
+ if (ret < 0) {
+ LOG(ERROR) << "Failed to send request: " << strerror(errno);
+ HANDLE_EINTR(close(host_socket));
+ return -1;
+ }
+
+ bytes_sent += ret;
+ bytes_remaining -= ret;
+ }
+
+ const size_t kAdbStatusLength = 4;
+ char response[kBufferMaxLength];
+ int response_length = HANDLE_EINTR(recv(host_socket, response,
+ kBufferMaxLength, 0));
+ if (response_length < kAdbStatusLength ||
+ strncmp("OKAY", response, kAdbStatusLength) != 0) {
+ char fail_msg_buffer[kBufferMaxLength * 3 + 1];
+ char* p = fail_msg_buffer;
+ for (int i = 0; i < response_length; ++i) {
+ snprintf(p, 3, "%02x,", static_cast<unsigned char>(response[i]));
+ p += 3;
+ }
+
+ if (p > fail_msg_buffer)
+ *(--p) = 0; // Eliminate the last comma.
+ LOG(ERROR) << "Bad response from ADB: length: " << response_length
+ << " data: " << fail_msg_buffer;
+ HANDLE_EINTR(close(host_socket));
+ return -1;
+ }
+
+ return host_socket;
+}
+
+} // namespace tools
+
diff --git a/tools/android/common/adb_connection.h b/tools/android/common/adb_connection.h
new file mode 100644
index 0000000..48a1a6f
--- /dev/null
+++ b/tools/android/common/adb_connection.h
@@ -0,0 +1,19 @@
+// 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_COMMON_ADB_CONNECTION_H__
+#define TOOLS_ANDROID_COMMON_ADB_CONNECTION_H__
+#pragma once
+
+namespace tools {
+
+// Creates a socket that can forward to a host socket through ADB.
+// The format of forward_to is <port>:<ip_address>.
+// Returns the socket handle, or -1 on any error.
+int ConnectAdbHostSocket(const char* forward_to);
+
+} // namespace tools
+
+#endif // TOOLS_ANDROID_COMMON_ADB_CONNECTION_H__
+
diff --git a/tools/android/common/common.gyp b/tools/android/common/common.gyp
new file mode 100644
index 0000000..54e2976
--- /dev/null
+++ b/tools/android/common/common.gyp
@@ -0,0 +1,27 @@
+# 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'android_tools_common',
+ 'type': '<(library)',
+ 'include_dirs': [
+ '..',
+ '../../..',
+ ],
+ 'sources': [
+ 'adb_connection.cc',
+ 'daemon.cc',
+ 'net.cc',
+ ],
+ },
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/tools/android/common/daemon.cc b/tools/android/common/daemon.cc
new file mode 100644
index 0000000..c332b1f
--- /dev/null
+++ b/tools/android/common/daemon.cc
@@ -0,0 +1,70 @@
+// 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/common/daemon.h"
+
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/command_line.h"
+#include "base/eintr_wrapper.h"
+#include "base/logging.h"
+
+namespace {
+
+const char kNoSpawnDaemon[] = "D";
+
+int g_exit_status = 0;
+
+void Exit(int unused) {
+ _exit(g_exit_status);
+}
+
+} // namespace
+
+namespace tools {
+
+bool HasHelpSwitch(const CommandLine& command_line) {
+ return command_line.HasSwitch("h") || command_line.HasSwitch("help");
+}
+
+bool HasNoSpawnDaemonSwitch(const CommandLine& command_line) {
+ return command_line.HasSwitch(kNoSpawnDaemon);
+}
+
+void ShowHelp(const char* program,
+ const char* extra_title,
+ const char* extra_descriptions) {
+ printf("Usage: %s [-%s] %s\n"
+ " -%s stops from spawning a daemon process\n%s",
+ program, kNoSpawnDaemon, extra_title, kNoSpawnDaemon,
+ extra_descriptions);
+}
+
+void SpawnDaemon(int exit_status) {
+ g_exit_status = exit_status;
+ signal(SIGUSR1, Exit);
+
+ if (fork()) {
+ // In parent process.
+ sleep(10); // Wait for the child process to finish setsid().
+ NOTREACHED();
+ }
+
+ // In child process.
+ setsid(); // Detach the child process from its parent.
+ kill(getppid(), SIGUSR1); // Inform the parent process to exit.
+
+ // Close the standard input and outputs, otherwise the process may block
+ // adbd when the shell exits.
+ // Comment out these lines if you want to see outputs for debugging.
+ HANDLE_EINTR(close(0));
+ HANDLE_EINTR(close(1));
+ HANDLE_EINTR(close(2));
+}
+
+} // namespace tools
+
diff --git a/tools/android/common/daemon.h b/tools/android/common/daemon.h
new file mode 100644
index 0000000..2bd25d4
--- /dev/null
+++ b/tools/android/common/daemon.h
@@ -0,0 +1,31 @@
+// 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_COMMON_DAEMON_H__
+#define TOOLS_ANDROID_COMMON_DAEMON_H__
+#pragma once
+
+#include <string>
+#include <vector>
+
+class CommandLine;
+
+namespace tools {
+
+bool HasHelpSwitch(const CommandLine& command_line);
+
+bool HasNoSpawnDaemonSwitch(const CommandLine& command_line);
+
+void ShowHelp(const char* program,
+ const char* extra_title,
+ const char* extra_descriptions);
+
+// Spawns a daemon process and exit the current process.
+// Any code after this function will be executed in the spawned daemon process.
+void SpawnDaemon(int exit_status);
+
+} // namespace tools
+
+#endif // TOOLS_ANDROID_COMMON_DAEMON_H__
+
diff --git a/tools/android/common/net.cc b/tools/android/common/net.cc
new file mode 100644
index 0000000..e50a18a
--- /dev/null
+++ b/tools/android/common/net.cc
@@ -0,0 +1,25 @@
+// 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/common/net.h"
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+namespace tools {
+
+int DisableNagle(int socket) {
+ int on = 1;
+ return setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+}
+
+int DeferAccept(int socket) {
+ int on = 1;
+ return setsockopt(socket, IPPROTO_TCP, TCP_DEFER_ACCEPT, &on, sizeof(on));
+}
+
+} // namespace tools
+
diff --git a/tools/android/common/net.h b/tools/android/common/net.h
new file mode 100644
index 0000000..d17b6f4
--- /dev/null
+++ b/tools/android/common/net.h
@@ -0,0 +1,21 @@
+// 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_COMMON_NET_H_
+#define TOOLS_ANDROID_COMMON_NET_H_
+#pragma once
+
+namespace tools {
+
+// DisableNagle can improve TCP transmission performance. Both Chrome net stack
+// and adb tool use it.
+int DisableNagle(int socket);
+
+// Wake up listener only when data arrive.
+int DeferAccept(int socket);
+
+} // namespace tools
+
+#endif // TOOLS_ANDROID_COMMON_NET_H_
+
diff --git a/tools/android/forwarder/forwarder.cc b/tools/android/forwarder/forwarder.cc
new file mode 100644
index 0000000..e19fe65
--- /dev/null
+++ b/tools/android/forwarder/forwarder.cc
@@ -0,0 +1,405 @@
+// 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 <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/command_line.h"
+#include "base/eintr_wrapper.h"
+#include "base/logging.h"
+#include "tools/android/common/adb_connection.h"
+#include "tools/android/common/daemon.h"
+#include "tools/android/common/net.h"
+
+namespace {
+
+const pthread_t kInvalidThread = static_cast<pthread_t>(-1);
+volatile bool g_killed = false;
+
+class Buffer {
+ public:
+ Buffer()
+ : bytes_read_(0),
+ write_offset_(0) {
+ }
+
+ bool CanRead() {
+ return bytes_read_ == 0;
+ }
+
+ bool CanWrite() {
+ return write_offset_ < bytes_read_;
+ }
+
+ int Read(int fd) {
+ int ret = -1;
+ if (CanRead()) {
+ ret = HANDLE_EINTR(read(fd, buffer_, kBufferSize));
+ if (ret > 0)
+ bytes_read_ = ret;
+ }
+ return ret;
+ }
+
+ int Write(int fd) {
+ int ret = -1;
+ if (CanWrite()) {
+ ret = HANDLE_EINTR(write(fd, buffer_ + write_offset_,
+ bytes_read_ - write_offset_));
+ if (ret > 0) {
+ write_offset_ += ret;
+ if (write_offset_ == bytes_read_) {
+ write_offset_ = 0;
+ bytes_read_ = 0;
+ }
+ }
+ }
+ return ret;
+ }
+
+ private:
+ // A big buffer to let our file-over-http bridge work more like real file.
+ static const int kBufferSize = 1024 * 128;
+ int bytes_read_;
+ int write_offset_;
+ char buffer_[kBufferSize];
+
+ DISALLOW_COPY_AND_ASSIGN(Buffer);
+};
+
+class Server;
+
+struct ForwarderThreadInfo {
+ ForwarderThreadInfo(Server* a_server, int a_forwarder_index)
+ : server(a_server),
+ forwarder_index(a_forwarder_index) {
+ }
+ Server* server;
+ int forwarder_index;
+};
+
+struct ForwarderInfo {
+ time_t start_time;
+ int socket1;
+ time_t socket1_last_byte_time;
+ size_t socket1_bytes;
+ int socket2;
+ time_t socket2_last_byte_time;
+ size_t socket2_bytes;
+};
+
+class Server {
+ public:
+ Server()
+ : thread_(kInvalidThread),
+ socket_(-1) {
+ memset(forward_to_, 0, sizeof(forward_to_));
+ memset(&forwarders_, 0, sizeof(forwarders_));
+ }
+
+ int GetFreeForwarderIndex() {
+ for (int i = 0; i < kMaxForwarders; i++) {
+ if (forwarders_[i].start_time == 0)
+ return i;
+ }
+ return -1;
+ }
+
+ void DisposeForwarderInfo(int index) {
+ forwarders_[index].start_time = 0;
+ }
+
+ ForwarderInfo* GetForwarderInfo(int index) {
+ return &forwarders_[index];
+ }
+
+ void DumpInformation() {
+ LOG(INFO) << "Server information: " << forward_to_;
+ LOG(INFO) << "No.: age up(bytes,idle) down(bytes,idle)";
+ int count = 0;
+ time_t now = time(NULL);
+ for (int i = 0; i < kMaxForwarders; i++) {
+ const ForwarderInfo& info = forwarders_[i];
+ if (info.start_time) {
+ count++;
+ LOG(INFO) << count << ": " << now - info.start_time << " up("
+ << info.socket1_bytes << ","
+ << now - info.socket1_last_byte_time << " down("
+ << info.socket2_bytes << ","
+ << now - info.socket2_last_byte_time << ")";
+ }
+ }
+ }
+
+ void Shutdown() {
+ if (socket_ >= 0)
+ shutdown(socket_, SHUT_RDWR);
+ }
+
+ bool InitSocket(const char* arg);
+
+ void StartThread() {
+ pthread_create(&thread_, NULL, ServerThread, this);
+ }
+
+ void JoinThread() {
+ if (thread_ != kInvalidThread)
+ pthread_join(thread_, NULL);
+ }
+
+ private:
+ static void* ServerThread(void* arg);
+
+ // There are 3 kinds of threads that will access the array:
+ // 1. Server thread will get a free ForwarderInfo and initialize it;
+ // 2. Forwarder threads will dispose the ForwarderInfo when it finishes;
+ // 3. Main thread will iterate and print the forwarders.
+ // Using an array is not optimal, but can avoid locks or other complex
+ // inter-thread communication.
+ static const int kMaxForwarders = 512;
+ ForwarderInfo forwarders_[kMaxForwarders];
+
+ pthread_t thread_;
+ int socket_;
+ char forward_to_[40];
+
+ DISALLOW_COPY_AND_ASSIGN(Server);
+};
+
+// Forwards all outputs from one socket to another socket.
+void* ForwarderThread(void* arg) {
+ ForwarderThreadInfo* thread_info =
+ reinterpret_cast<ForwarderThreadInfo*>(arg);
+ Server* server = thread_info->server;
+ int index = thread_info->forwarder_index;
+ delete thread_info;
+ ForwarderInfo* info = server->GetForwarderInfo(index);
+ int socket1 = info->socket1;
+ int socket2 = info->socket2;
+ int nfds = socket1 > socket2 ? socket1 + 1 : socket2 + 1;
+ fd_set read_fds;
+ fd_set write_fds;
+ Buffer buffer1;
+ Buffer buffer2;
+
+ while (!g_killed) {
+ FD_ZERO(&read_fds);
+ if (buffer1.CanRead())
+ FD_SET(socket1, &read_fds);
+ if (buffer2.CanRead())
+ FD_SET(socket2, &read_fds);
+
+ FD_ZERO(&write_fds);
+ if (buffer1.CanWrite())
+ FD_SET(socket2, &write_fds);
+ if (buffer2.CanWrite())
+ FD_SET(socket1, &write_fds);
+
+ if (HANDLE_EINTR(select(nfds, &read_fds, &write_fds, NULL, NULL)) <= 0) {
+ LOG(ERROR) << "Select error: " << strerror(errno);
+ break;
+ }
+
+ int now = time(NULL);
+ if (FD_ISSET(socket1, &read_fds)) {
+ info->socket1_last_byte_time = now;
+ int bytes = buffer1.Read(socket1);
+ if (bytes <= 0)
+ break;
+ info->socket1_bytes += bytes;
+ }
+ if (FD_ISSET(socket2, &read_fds)) {
+ info->socket2_last_byte_time = now;
+ int bytes = buffer2.Read(socket2);
+ if (bytes <= 0)
+ break;
+ info->socket2_bytes += bytes;
+ }
+ if (FD_ISSET(socket1, &write_fds)) {
+ if (buffer2.Write(socket1) <= 0)
+ break;
+ }
+ if (FD_ISSET(socket2, &write_fds)) {
+ if (buffer1.Write(socket2) <= 0)
+ break;
+ }
+ }
+
+ HANDLE_EINTR(close(socket1));
+ HANDLE_EINTR(close(socket2));
+ server->DisposeForwarderInfo(index);
+ return NULL;
+}
+
+// Listens to a server socket. On incoming request, forward it to the host.
+// static
+void* Server::ServerThread(void* arg) {
+ Server* server = reinterpret_cast<Server*>(arg);
+ while (!g_killed) {
+ int forwarder_index = server->GetFreeForwarderIndex();
+ if (forwarder_index < 0) {
+ LOG(ERROR) << "Too many forwarders";
+ continue;
+ }
+
+ struct sockaddr_in addr;
+ socklen_t addr_len = sizeof(addr);
+ int socket = HANDLE_EINTR(accept(server->socket_,
+ reinterpret_cast<sockaddr*>(&addr),
+ &addr_len));
+ if (socket < 0) {
+ LOG(ERROR) << "Failed to accept: " << strerror(errno);
+ break;
+ }
+ tools::DisableNagle(socket);
+
+ int host_socket = tools::ConnectAdbHostSocket(server->forward_to_);
+ if (host_socket >= 0) {
+ // Set NONBLOCK flag because we use select().
+ fcntl(socket, F_SETFL, fcntl(socket, F_GETFL) | O_NONBLOCK);
+ fcntl(host_socket, F_SETFL, fcntl(host_socket, F_GETFL) | O_NONBLOCK);
+
+ ForwarderInfo* forwarder_info = server->GetForwarderInfo(forwarder_index);
+ time_t now = time(NULL);
+ forwarder_info->start_time = now;
+ forwarder_info->socket1 = socket;
+ forwarder_info->socket1_last_byte_time = now;
+ forwarder_info->socket1_bytes = 0;
+ forwarder_info->socket2 = host_socket;
+ forwarder_info->socket2_last_byte_time = now;
+ forwarder_info->socket2_bytes = 0;
+
+ pthread_t thread;
+ pthread_create(&thread, NULL, ForwarderThread,
+ new ForwarderThreadInfo(server, forwarder_index));
+ } else {
+ // Close the unused client socket which is failed to connect to host.
+ HANDLE_EINTR(close(socket));
+ }
+ }
+
+ HANDLE_EINTR(close(server->socket_));
+ server->socket_ = -1;
+ return NULL;
+}
+
+// Format of arg: <Device port>[:<Forward to port>:<Forward to address>]
+bool Server::InitSocket(const char* arg) {
+ char* endptr;
+ int local_port = static_cast<int>(strtol(arg, &endptr, 10));
+ if (local_port <= 0)
+ return false;
+
+ if (*endptr != ':') {
+ snprintf(forward_to_, sizeof(forward_to_), "%d:127.0.0.1", local_port);
+ } else {
+ strncpy(forward_to_, endptr + 1, sizeof(forward_to_) - 1);
+ }
+
+ socket_ = socket(AF_INET, SOCK_STREAM, 0);
+ if (socket_ < 0) {
+ perror("server socket");
+ return false;
+ }
+ tools::DisableNagle(socket_);
+
+ sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr.sin_port = htons(local_port);
+ int reuse_addr = 1;
+ setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR,
+ &reuse_addr, sizeof(reuse_addr));
+ tools::DeferAccept(socket_);
+ if (HANDLE_EINTR(bind(socket_, reinterpret_cast<sockaddr*>(&addr),
+ sizeof(addr))) < 0 ||
+ HANDLE_EINTR(listen(socket_, 5)) < 0) {
+ perror("server bind");
+ HANDLE_EINTR(close(socket_));
+ socket_ = -1;
+ return false;
+ }
+
+ printf("Forwarding device port %d to host %s\n", local_port, forward_to_);
+ return true;
+}
+
+int g_server_count = 0;
+Server* g_servers = NULL;
+
+void KillHandler(int unused) {
+ g_killed = true;
+ for (int i = 0; i < g_server_count; i++)
+ g_servers[i].Shutdown();
+}
+
+void DumpInformation(int unused) {
+ for (int i = 0; i < g_server_count; i++)
+ g_servers[i].DumpInformation();
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ printf("Android device to host TCP forwarder\n");
+ printf("Like 'adb forward' but in the reverse direction\n");
+
+ CommandLine command_line(argc, argv);
+ CommandLine::StringVector server_args = command_line.GetArgs();
+ if (tools::HasHelpSwitch(command_line) || server_args.empty()) {
+ tools::ShowHelp(
+ argv[0],
+ "<Device port>[:<Forward to port>:<Forward to address>] ...",
+ " <Forward to port> default is <Device port>\n"
+ " <Forward to address> default is 127.0.0.1\n");
+ return 0;
+ }
+
+ g_servers = new Server[server_args.size()];
+ g_server_count = 0;
+ int failed_count = 0;
+ for (size_t i = 0; i < server_args.size(); i++) {
+ if (!g_servers[g_server_count].InitSocket(server_args[i].c_str())) {
+ printf("Couldn't start forwarder server for port spec: %s\n",
+ server_args[i].c_str());
+ ++failed_count;
+ } else {
+ ++g_server_count;
+ }
+ }
+
+ if (g_server_count == 0) {
+ printf("No forwarder servers could be started. Exiting.\n");
+ delete [] g_servers;
+ return failed_count;
+ }
+
+ if (!tools::HasNoSpawnDaemonSwitch(command_line))
+ tools::SpawnDaemon(failed_count);
+
+ signal(SIGTERM, KillHandler);
+ signal(SIGUSR2, DumpInformation);
+
+ for (int i = 0; i < g_server_count; i++)
+ g_servers[i].StartThread();
+ for (int i = 0; i < g_server_count; i++)
+ g_servers[i].JoinThread();
+ g_server_count = 0;
+ delete [] g_servers;
+
+ return 0;
+}
+
diff --git a/tools/android/forwarder/forwarder.gyp b/tools/android/forwarder/forwarder.gyp
new file mode 100644
index 0000000..75390c3
--- /dev/null
+++ b/tools/android/forwarder/forwarder.gyp
@@ -0,0 +1,47 @@
+# 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.
+{
+ 'targets': [
+ {
+ 'target_name': 'forwarder',
+ 'type': 'none',
+ 'dependencies': [
+ 'forwarder_symbols',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'strip_forwarder',
+ 'inputs': ['<(PRODUCT_DIR)/forwarder_symbols'],
+ 'outputs': ['<(PRODUCT_DIR)/forwarder'],
+ 'action': [
+ '<!(/bin/echo -n $STRIP)',
+ '--strip-unneeded',
+ '<@(_inputs)',
+ '-o',
+ '<@(_outputs)',
+ ],
+ },
+ ],
+ }, {
+ 'target_name': 'forwarder_symbols',
+ 'type': 'executable',
+ 'dependencies': [
+ '../../../base/base.gyp:base',
+ '../common/common.gyp:android_tools_common',
+ ],
+ 'include_dirs': [
+ '../../..',
+ ],
+ 'sources': [
+ 'forwarder.cc',
+ ],
+ },
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2: