diff --git a/adb/adb.c b/adb/adb.c index 04785fdf7..dd40eefe9 100644 --- a/adb/adb.c +++ b/adb/adb.c @@ -967,6 +967,99 @@ int adb_main(int is_daemon, int server_port) return 0; } +#if ADB_HOST +void connect_device(char* host, char* buffer, int buffer_size) +{ + int port, fd; + char* portstr = strchr(host, ':'); + char buf[4096]; + + if (!portstr) { + snprintf(buffer, buffer_size, "unable to parse %s as :", host); + return; + } + if (find_transport(host)) { + snprintf(buffer, buffer_size, "already connected to %s", host); + return; + } + + // zero terminate host by overwriting the ':' + *portstr++ = 0; + if (sscanf(portstr, "%d", &port) == 0) { + snprintf(buffer, buffer_size, "bad port number %s", portstr); + return; + } + + fd = socket_network_client(host, port, SOCK_STREAM); + if (fd < 0) { + snprintf(buffer, buffer_size, "unable to connect to %s:%d", host, port); + return; + } + + D("client: connected on remote on fd %d\n", fd); + close_on_exec(fd); + disable_tcp_nagle(fd); + snprintf(buf, sizeof buf, "%s:%d", host, port); + register_socket_transport(fd, buf, port, 0); + snprintf(buffer, buffer_size, "connected to %s:%d", host, port); +} + +void connect_emulator(char* port_spec, char* buffer, int buffer_size) +{ + char* port_separator = strchr(port_spec, ','); + if (!port_separator) { + snprintf(buffer, buffer_size, + "unable to parse '%s' as ,", + port_spec); + return; + } + + // Zero-terminate console port and make port_separator point to 2nd port. + *port_separator++ = 0; + int console_port = strtol(port_spec, NULL, 0); + int adb_port = strtol(port_separator, NULL, 0); + if (!(console_port > 0 && adb_port > 0)) { + *(port_separator - 1) = ','; + snprintf(buffer, buffer_size, + "Invalid port numbers: Expected positive numbers, got '%s'", + port_spec); + return; + } + + /* Check if the emulator is already known. + * Note: There's a small but harmless race condition here: An emulator not + * present just yet could be registered by another invocation right + * after doing this check here. However, local_connect protects + * against double-registration too. From here, a better error message + * can be produced. In the case of the race condition, the very specific + * error message won't be shown, but the data doesn't get corrupted. */ + atransport* known_emulator = find_emulator_transport_by_adb_port(adb_port); + if (known_emulator != NULL) { + snprintf(buffer, buffer_size, + "Emulator on port %d already registered.", adb_port); + return; + } + + /* Check if more emulators can be registered. Similar unproblematic + * race condition as above. */ + int candidate_slot = get_available_local_transport_index(); + if (candidate_slot < 0) { + snprintf(buffer, buffer_size, "Cannot accept more emulators."); + return; + } + + /* Preconditions met, try to connect to the emulator. */ + if (!local_connect_arbitrary_ports(console_port, adb_port)) { + snprintf(buffer, buffer_size, + "Connected to emulator on ports %d,%d", console_port, adb_port); + } else { + snprintf(buffer, buffer_size, + "Could not connect to emulator on ports %d,%d", + console_port, adb_port); + } +} +#endif + int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s) { atransport *transport = NULL; @@ -1024,43 +1117,16 @@ int handle_host_request(char *service, transport_type ttype, char* serial, int r return 0; } - // add a new TCP transport + // add a new TCP transport, device or emulator if (!strncmp(service, "connect:", 8)) { char buffer[4096]; - int port, fd; char* host = service + 8; - char* portstr = strchr(host, ':'); - - if (!portstr) { - snprintf(buffer, sizeof(buffer), "unable to parse %s as :", host); - goto done; + if (!strncmp(host, "emu:", 4)) { + connect_emulator(host + 4, buffer, sizeof(buffer)); + } else { + connect_device(host, buffer, sizeof(buffer)); } - if (find_transport(host)) { - snprintf(buffer, sizeof(buffer), "Already connected to %s", host); - goto done; - } - - // zero terminate host by overwriting the ':' - *portstr++ = 0; - if (sscanf(portstr, "%d", &port) == 0) { - snprintf(buffer, sizeof(buffer), "bad port number %s", portstr); - goto done; - } - - fd = socket_network_client(host, port, SOCK_STREAM); - if (fd < 0) { - snprintf(buffer, sizeof(buffer), "unable to connect to %s:%d", host, port); - goto done; - } - - D("client: connected on remote on fd %d\n", fd); - close_on_exec(fd); - disable_tcp_nagle(fd); - snprintf(buf, sizeof buf, "%s:%d", host, port); - register_socket_transport(fd, buf, port, 0); - snprintf(buffer, sizeof(buffer), "connected to %s:%d", host, port); - -done: + // Send response for emulator and device snprintf(buf, sizeof(buf), "OKAY%04x%s",(unsigned)strlen(buffer), buffer); writex(reply_fd, buf, strlen(buf)); return 0; diff --git a/adb/adb.h b/adb/adb.h index a2b611e6f..292e41592 100644 --- a/adb/adb.h +++ b/adb/adb.h @@ -183,6 +183,7 @@ struct atransport /* used to identify transports for clients */ char *serial; char *product; + int adb_port; // Use for emulators (local transport) /* a list of adisconnect callbacks called when the transport is kicked */ int kicked; @@ -262,6 +263,9 @@ void run_transport_disconnects( atransport* t ); void kick_transport( atransport* t ); /* initialize a transport object's func pointers and state */ +#if ADB_HOST +int get_available_local_transport_index(); +#endif int init_socket_transport(atransport *t, int s, int port, int local); void init_usb_transport(atransport *t, usb_handle *usb, int state); @@ -280,6 +284,9 @@ void register_usb_transport(usb_handle *h, const char *serial, unsigned writeabl void unregister_usb_transport(usb_handle *usb); atransport *find_transport(const char *serial); +#if ADB_HOST +atransport* find_emulator_transport_by_adb_port(int adb_port); +#endif int service_to_fd(const char *name); #if ADB_HOST @@ -368,6 +375,7 @@ typedef enum { void local_init(int port); int local_connect(int port); +int local_connect_arbitrary_ports(int console_port, int adb_port); /* usb host/client interface */ void usb_init(); diff --git a/adb/transport_local.c b/adb/transport_local.c index cfd3b4bcc..8dfc98dfd 100644 --- a/adb/transport_local.c +++ b/adb/transport_local.c @@ -41,9 +41,9 @@ static inline void fix_endians(apacket *p) #endif #if ADB_HOST -/* we keep a list of opened transports, transport 0 is bound to 5555, - * transport 1 to 5557, .. transport n to 5555 + n*2. the list is used - * to detect when we're trying to connect twice to a given local transport +/* we keep a list of opened transports. The atransport struct knows to which + * local transport it is connected. The list is used to detect when we're + * trying to connect twice to a given local transport. */ #define ADB_LOCAL_TRANSPORT_MAX 16 @@ -102,7 +102,11 @@ static int remote_write(apacket *p, atransport *t) } -int local_connect(int port) +int local_connect(int port) { + return local_connect_arbitrary_ports(port-1, port); +} + +int local_connect_arbitrary_ports(int console_port, int adb_port) { char buf[64]; int fd = -1; @@ -110,19 +114,19 @@ int local_connect(int port) #if ADB_HOST const char *host = getenv("ADBHOST"); if (host) { - fd = socket_network_client(host, port, SOCK_STREAM); + fd = socket_network_client(host, adb_port, SOCK_STREAM); } #endif if (fd < 0) { - fd = socket_loopback_client(port, SOCK_STREAM); + fd = socket_loopback_client(adb_port, SOCK_STREAM); } if (fd >= 0) { D("client: connected on remote on fd %d\n", fd); close_on_exec(fd); disable_tcp_nagle(fd); - snprintf(buf, sizeof buf, "%s%d", LOCAL_CLIENT_PREFIX, port - 1); - register_socket_transport(fd, buf, port, 1); + snprintf(buf, sizeof buf, "%s%d", LOCAL_CLIENT_PREFIX, console_port); + register_socket_transport(fd, buf, adb_port, 1); return 0; } return -1; @@ -227,7 +231,50 @@ static void remote_close(atransport *t) adb_close(t->fd); } -int init_socket_transport(atransport *t, int s, int port, int local) + +#if ADB_HOST +/* Only call this function if you already hold local_transports_lock. */ +atransport* find_emulator_transport_by_adb_port_locked(int adb_port) +{ + int i; + for (i = 0; i < ADB_LOCAL_TRANSPORT_MAX; i++) { + if (local_transports[i] && local_transports[i]->adb_port == adb_port) { + return local_transports[i]; + } + } + return NULL; +} + +atransport* find_emulator_transport_by_adb_port(int adb_port) +{ + adb_mutex_lock( &local_transports_lock ); + atransport* result = find_emulator_transport_by_adb_port_locked(adb_port); + adb_mutex_unlock( &local_transports_lock ); + return result; +} + +/* Only call this function if you already hold local_transports_lock. */ +int get_available_local_transport_index_locked() +{ + int i; + for (i = 0; i < ADB_LOCAL_TRANSPORT_MAX; i++) { + if (local_transports[i] == NULL) { + return i; + } + } + return -1; +} + +int get_available_local_transport_index() +{ + adb_mutex_lock( &local_transports_lock ); + int result = get_available_local_transport_index_locked(); + adb_mutex_unlock( &local_transports_lock ); + return result; +} +#endif + +int init_socket_transport(atransport *t, int s, int adb_port, int local) { int fail = 0; @@ -239,26 +286,30 @@ int init_socket_transport(atransport *t, int s, int port, int local) t->sync_token = 1; t->connection_state = CS_OFFLINE; t->type = kTransportLocal; + t->adb_port = 0; #if ADB_HOST if (HOST && local) { adb_mutex_lock( &local_transports_lock ); { - int index = (port - DEFAULT_ADB_LOCAL_TRANSPORT_PORT)/2; - - if (!(port & 1) || index < 0 || index >= ADB_LOCAL_TRANSPORT_MAX) { - D("bad local transport port number: %d\n", port); - fail = -1; - } - else if (local_transports[index] != NULL) { + t->adb_port = adb_port; + atransport* existing_transport = + find_emulator_transport_by_adb_port_locked(adb_port); + int index = get_available_local_transport_index_locked(); + if (existing_transport != NULL) { D("local transport for port %d already registered (%p)?\n", - port, local_transports[index]); + adb_port, existing_transport); fail = -1; - } - else + } else if (index < 0) { + // Too many emulators. + D("cannot register more emulators. Maximum is %d\n", + ADB_LOCAL_TRANSPORT_MAX); + fail = -1; + } else { local_transports[index] = t; - } - adb_mutex_unlock( &local_transports_lock ); + } + } + adb_mutex_unlock( &local_transports_lock ); } #endif return fail;