adb: Improved support for running adb over TCP/IP

Added new commands:

adb connect <host>:<port> (to connect to a device via TCP/IP)
adb tcpip <port>          (to restart adbd on the device to listen on TCP/IP)
adb usb                   (to restart adbd on the device to listen USB)

Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/adb.c b/adb.c
index e8d2c8f..9980bad 100644
--- a/adb.c
+++ b/adb.c
@@ -832,6 +832,7 @@
 {
 #if !ADB_HOST
     int secure = 0;
+    int port;
     char value[PROPERTY_VALUE_MAX];
 #endif
 
@@ -918,14 +919,17 @@
     }
 
         /* for the device, start the usb transport if the
-        ** android usb device exists, otherwise start the
-        ** network transport.
+        ** android usb device exists and "service.adb.tcp"
+        ** is not set, otherwise start the network transport.
         */
-    if(access("/dev/android_adb", F_OK) == 0 ||
-       access("/dev/android", F_OK) == 0) {
+    property_get("service.adb.tcp.port", value, "0");
+    if (sscanf(value, "%d", &port) == 0) {
+        port = 0;
+    }
+    if (port == 0 && access("/dev/android_adb", F_OK) == 0) {
         usb_init();
     } else {
-        local_init();
+        local_init(port);
     }
     init_jdwp();
 #endif
@@ -1006,6 +1010,43 @@
         return 0;
     }
 
+    // add a new TCP transport
+    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>:<port>\n", 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\n", portstr);
+            goto done;
+        }
+
+        fd = socket_network_client(host, port, SOCK_STREAM);
+        if (fd < 0) {
+            snprintf(buffer, sizeof(buffer), "unable to connect to %s:%d\n", 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\n", host, port);
+
+done:
+        snprintf(buf, sizeof(buf), "OKAY%04x%s",(unsigned)strlen(buffer),buffer);
+        writex(reply_fd, buf, strlen(buf));
+        return 0;
+    }
+
     // returns our value for ADB_SERVER_VERSION
     if (!strcmp(service, "version")) {
         char version[12];
diff --git a/adb.h b/adb.h
index 4789edf..d782751 100644
--- a/adb.h
+++ b/adb.h
@@ -33,7 +33,7 @@
 #define ADB_VERSION_MAJOR 1         // Used for help/version information
 #define ADB_VERSION_MINOR 0         // Used for help/version information
 
-#define ADB_SERVER_VERSION    23    // Increment this when we want to force users to start a new adb server
+#define ADB_SERVER_VERSION    24    // Increment this when we want to force users to start a new adb server
 
 typedef struct amessage amessage;
 typedef struct apacket apacket;
@@ -262,14 +262,14 @@
 void   kick_transport( atransport*  t );
 
 /* initialize a transport object's func pointers and state */
-int  init_socket_transport(atransport *t, int s, int port);
+int  init_socket_transport(atransport *t, int s, int port, int local);
 void init_usb_transport(atransport *t, usb_handle *usb, int state);
 
 /* for MacOS X cleanup */
 void close_usb_devices();
 
 /* cause new transports to be init'd and added to the list */
-void register_socket_transport(int s, const char *serial, int  port);
+void register_socket_transport(int s, const char *serial, int port, int local);
 void register_usb_transport(usb_handle *h, const char *serial, unsigned writeable);
 
 /* this should only be used for transports with connection_state == CS_NOPERM */
diff --git a/commandline.c b/commandline.c
index 4ec5c8b..5d3187a 100644
--- a/commandline.c
+++ b/commandline.c
@@ -105,6 +105,7 @@
         "                                 environment variable is used, which must\n"
         "                                 be an absolute path.\n"
         " devices                       - list all connected devices\n"
+        " connect <host>:<port>         - connect to a device via TCP/IP"
         "\n"
         "device commands:\n"
         "  adb push <local> <remote>    - copy file/dir to device\n"
@@ -149,7 +150,9 @@
         "  adb status-window            - continuously print device status for a specified device\n"
         "  adb remount                  - remounts the /system partition on the device read-write\n"
         "  adb reboot [bootloader|recovery] - reboots the device, optionally into the bootloader or recovery program\n"
-        "  adb root                     - restarts adb with root permissions\n"
+        "  adb root                     - restarts the adbd daemon with root permissions\n"
+        "  adb usb                      - restarts the adbd daemon listening on USB"
+        "  adb tcpip <port>             - restarts the adbd daemon listening on TCP on the specified port"
         "\n"
         "networking:\n"
         "  adb ppp <tty> [parameters]   - Run PPP over USB.\n"
@@ -850,6 +853,22 @@
         }
     }
 
+    if(!strcmp(argv[0], "connect")) {
+        char *tmp;
+        if (argc != 2) {
+            fprintf(stderr, "Usage: adb connect <host>:<port>\n");
+            return 1;
+        }
+        snprintf(buf, sizeof buf, "host:%s:%s", argv[0], argv[1]);
+        tmp = adb_query(buf);
+        if(tmp) {
+            printf("%s\n", tmp);
+            return 0;
+        } else {
+            return 1;
+        }
+    }
+
     if (!strcmp(argv[0], "emu")) {
         return adb_send_emulator_command(argc, argv);
     }
@@ -908,35 +927,15 @@
         return 0;
     }
 
-    if(!strcmp(argv[0], "remount")) {
-        int fd = adb_connect("remount:");
-        if(fd >= 0) {
-            read_and_dump(fd);
-            adb_close(fd);
-            return 0;
-        }
-        fprintf(stderr,"error: %s\n", adb_error());
-        return 1;
-    }
-
-    if(!strcmp(argv[0], "reboot")) {
-        int fd;
+    if(!strcmp(argv[0], "remount") || !strcmp(argv[0], "reboot")
+            || !strcmp(argv[0], "tcpip") || !strcmp(argv[0], "usb")
+            || !strcmp(argv[0], "reboot")) {
+        char command[100];
         if (argc > 1)
-            snprintf(buf, sizeof(buf), "reboot:%s", argv[1]);
+            snprintf(command, sizeof(command), "%s:%s", argv[0], argv[1]);
         else
-            snprintf(buf, sizeof(buf), "reboot:");
-        fd = adb_connect(buf);
-        if(fd >= 0) {
-            read_and_dump(fd);
-            adb_close(fd);
-            return 0;
-        }
-        fprintf(stderr,"error: %s\n", adb_error());
-        return 1;
-    }
-
-    if(!strcmp(argv[0], "root")) {
-        int fd = adb_connect("root:");
+            snprintf(command, sizeof(command), "%s:", argv[0]);
+        int fd = adb_connect(command);
         if(fd >= 0) {
             read_and_dump(fd);
             adb_close(fd);
diff --git a/services.c b/services.c
index 517da55..2864ac9 100644
--- a/services.c
+++ b/services.c
@@ -120,6 +120,7 @@
         if (strcmp(value, "1") != 0) {
             snprintf(buf, sizeof(buf), "adbd cannot run as root in production builds\n");
             writex(fd, buf, strlen(buf));
+            adb_close(fd);
             return;
         }
 
@@ -134,13 +135,52 @@
     }
 }
 
-void reboot_service(int fd, char *arg)
+void restart_tcp_service(int fd, void *cookie)
+{
+    char buf[100];
+    char value[PROPERTY_VALUE_MAX];
+    int port = (int)cookie;
+
+    if (port <= 0) {
+        snprintf(buf, sizeof(buf), "invalid port\n");
+        writex(fd, buf, strlen(buf));
+        adb_close(fd);
+        return;
+    }
+
+    snprintf(value, sizeof(value), "%d", port);
+    property_set("service.adb.tcp.port", value);
+    snprintf(buf, sizeof(buf), "restarting in TCP mode port: %d\n", port);
+    writex(fd, buf, strlen(buf));
+    adb_close(fd);
+
+    // quit, and init will restart us in TCP mode
+    sleep(1);
+    exit(1);
+}
+
+void restart_usb_service(int fd, void *cookie)
+{
+    char buf[100];
+
+    property_set("service.adb.tcp.port", "0");
+    snprintf(buf, sizeof(buf), "restarting in USB mode\n");
+    writex(fd, buf, strlen(buf));
+    adb_close(fd);
+
+    // quit, and init will restart us in USB mode
+    sleep(1);
+    exit(1);
+}
+
+void reboot_service(int fd, void *arg)
 {
     char buf[100];
     int ret;
 
     sync();
-    ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, arg);
+    ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
+                    LINUX_REBOOT_CMD_RESTART2, (char *)arg);
     if (ret < 0) {
         snprintf(buf, sizeof(buf), "reboot failed: %s\n", strerror(errno));
         writex(fd, buf, strlen(buf));
@@ -415,12 +455,20 @@
     } else if(!strncmp(name, "remount:", 8)) {
         ret = create_service_thread(remount_service, NULL);
     } else if(!strncmp(name, "reboot:", 7)) {
-        char* arg = name + 7;
+        const char* arg = name + 7;
         if (*name == 0)
             arg = NULL;
-        ret = create_service_thread(reboot_service, arg);
+        ret = create_service_thread(reboot_service, (void *)arg);
     } else if(!strncmp(name, "root:", 5)) {
         ret = create_service_thread(restart_root_service, NULL);
+    } else if(!strncmp(name, "tcpip:", 6)) {
+        int port;
+        if (sscanf(name + 6, "%d", &port) == 0) {
+            port = 0;
+        }
+        ret = create_service_thread(restart_tcp_service, (void *)port);
+    } else if(!strncmp(name, "usb:", 4)) {
+        ret = create_service_thread(restart_usb_service, NULL);
 #endif
 #if 0
     } else if(!strncmp(name, "echo:", 5)){
diff --git a/transport.c b/transport.c
index c9e7752..617dabf 100644
--- a/transport.c
+++ b/transport.c
@@ -849,11 +849,11 @@
 }
 #endif // ADB_HOST
 
-void register_socket_transport(int s, const char *serial, int  port)
+void register_socket_transport(int s, const char *serial, int port, int local)
 {
     atransport *t = calloc(1, sizeof(atransport));
     D("transport: %p init'ing for socket %d, on port %d\n", t, s, port);
-    if ( init_socket_transport(t, s, port) < 0 ) {
+    if ( init_socket_transport(t, s, port, local) < 0 ) {
         adb_close(s);
         free(t);
         return;
diff --git a/transport_local.c b/transport_local.c
index be01f29..c528d1f 100644
--- a/transport_local.c
+++ b/transport_local.c
@@ -122,7 +122,7 @@
         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);
+        register_socket_transport(fd, buf, port, 1);
         return 0;
     }
     return -1;
@@ -147,17 +147,18 @@
     return 0;
 }
 
-static void *server_socket_thread(void *x)
+static void *server_socket_thread(void * arg)
 {
     int serverfd, fd;
     struct sockaddr addr;
     socklen_t alen;
+    int port = (int)arg;
 
     D("transport: server_socket_thread() starting\n");
     serverfd = -1;
     for(;;) {
         if(serverfd == -1) {
-            serverfd = socket_inaddr_any_server(ADB_LOCAL_TRANSPORT_PORT, SOCK_STREAM);
+            serverfd = socket_inaddr_any_server(port, SOCK_STREAM);
             if(serverfd < 0) {
                 D("server: cannot bind socket yet\n");
                 adb_sleep_ms(1000);
@@ -167,20 +168,20 @@
         }
 
         alen = sizeof(addr);
-        D("server: trying to get new connection from %d\n", ADB_LOCAL_TRANSPORT_PORT);
+        D("server: trying to get new connection from %d\n", port);
         fd = adb_socket_accept(serverfd, &addr, &alen);
         if(fd >= 0) {
             D("server: new connection on fd %d\n", fd);
             close_on_exec(fd);
             disable_tcp_nagle(fd);
-            register_socket_transport(fd,"host",ADB_LOCAL_TRANSPORT_PORT);
+            register_socket_transport(fd, "host", port, 1);
         }
     }
     D("transport: server_socket_thread() exiting\n");
     return 0;
 }
 
-void local_init(void)
+void local_init(int port)
 {
     adb_thread_t thr;
     void* (*func)(void *);
@@ -193,7 +194,7 @@
 
     D("transport: local %s init\n", HOST ? "client" : "server");
 
-    if(adb_thread_create(&thr, func, 0)) {
+    if(adb_thread_create(&thr, func, (void *)port)) {
         fatal_errno("cannot create local socket %s thread",
                     HOST ? "client" : "server");
     }
@@ -225,7 +226,7 @@
     adb_close(t->fd);
 }
 
-int init_socket_transport(atransport *t, int s, int  port)
+int init_socket_transport(atransport *t, int s, int port, int local)
 {
     int  fail = 0;
 
@@ -239,7 +240,7 @@
     t->type = kTransportLocal;
 
 #if ADB_HOST
-    if (HOST) {
+    if (HOST && local) {
         adb_mutex_lock( &local_transports_lock );
         {
             int  index = (port - ADB_LOCAL_TRANSPORT_PORT)/2;