adb: Improve ADB's forward redirection management.

This adds a few new options/modes to 'adb forward':

  adb forward --list
  adb forward --remove <local>
  adb forward --remove-all
  adb forward --no-rebind <local> <remote>

For more context, see http://code.google.com/p/android/issues/detail?id=39631

Note that this only affects the host adb client and server programs,
i.e. it's compatible with devices running older adbd versions.

Change-Id: I9cda3ba12b5a8560a2061620bc7f948e5c1e70f7
diff --git a/adb.c b/adb.c
index 07bfbe5..b3283de 100644
--- a/adb.c
+++ b/adb.c
@@ -722,24 +722,90 @@
     return -1;
 }
 
-static int remove_listener(const char *local_name, const char *connect_to, atransport* transport)
+// Write a single line describing a listener to a user-provided buffer.
+// Appends a trailing zero, even in case of truncation, but the function
+// returns the full line length.
+// If |buffer| is NULL, does not write but returns required size.
+static int format_listener(alistener* l, char* buffer, size_t buffer_len) {
+    // Format is simply:
+    //
+    //  <device-serial> " " <local-name> " " <remote-name> "\n"
+    //
+    int local_len = strlen(l->local_name);
+    int connect_len = strlen(l->connect_to);
+    int serial_len = strlen(l->transport->serial);
+
+    if (buffer != NULL) {
+        snprintf(buffer, buffer_len, "%s %s %s\n",
+                l->transport->serial, l->local_name, l->connect_to);
+    }
+    // NOTE: snprintf() on Windows returns -1 in case of truncation, so
+    // return the computed line length instead.
+    return local_len + connect_len + serial_len + 3;
+}
+
+// Write the list of current listeners (network redirections) into a
+// user-provided buffer. Appends a trailing zero, even in case of
+// trunctaion, but return the full size in bytes.
+// If |buffer| is NULL, does not write but returns required size.
+static int format_listeners(char* buf, size_t buflen)
+{
+    alistener* l;
+    int result = 0;
+    for (l = listener_list.next; l != &listener_list; l = l->next) {
+        // Ignore special listeners like those for *smartsocket*
+        if (l->connect_to[0] == '*')
+          continue;
+        int len = format_listener(l, buf, buflen);
+        // Ensure there is space for the trailing zero.
+        result += len;
+        if (buf != NULL) {
+          buf += len;
+          buflen -= len;
+          if (buflen <= 0)
+              break;
+        }
+    }
+    return result;
+}
+
+static int remove_listener(const char *local_name, atransport* transport)
 {
     alistener *l;
 
     for (l = listener_list.next; l != &listener_list; l = l->next) {
-        if (!strcmp(local_name, l->local_name) &&
-            !strcmp(connect_to, l->connect_to) &&
-            l->transport && l->transport == transport) {
-
-            listener_disconnect(l, transport);
+        if (!strcmp(local_name, l->local_name)) {
+            listener_disconnect(l, l->transport);
             return 0;
         }
     }
-
     return -1;
 }
 
-static int install_listener(const char *local_name, const char *connect_to, atransport* transport)
+static void remove_all_listeners(void)
+{
+    alistener *l, *l_next;
+    for (l = listener_list.next; l != &listener_list; l = l_next) {
+        l_next = l->next;
+        // Never remove smart sockets.
+        if (l->connect_to[0] == '*')
+            continue;
+        listener_disconnect(l, l->transport);
+    }
+}
+
+// error/status codes for install_listener.
+typedef enum {
+  INSTALL_STATUS_OK = 0,
+  INSTALL_STATUS_INTERNAL_ERROR = -1,
+  INSTALL_STATUS_CANNOT_BIND = -2,
+  INSTALL_STATUS_CANNOT_REBIND = -3,
+} install_status_t;
+
+static install_status_t install_listener(const char *local_name,
+                                         const char *connect_to,
+                                         atransport* transport,
+                                         int no_rebind)
 {
     alistener *l;
 
@@ -751,12 +817,17 @@
 
                 /* can't repurpose a smartsocket */
             if(l->connect_to[0] == '*') {
-                return -1;
+                return INSTALL_STATUS_INTERNAL_ERROR;
+            }
+
+                /* can't repurpose a listener if 'no_rebind' is true */
+            if (no_rebind) {
+                return INSTALL_STATUS_CANNOT_REBIND;
             }
 
             cto = strdup(connect_to);
             if(cto == 0) {
-                return -1;
+                return INSTALL_STATUS_INTERNAL_ERROR;
             }
 
             //printf("rebinding '%s' to '%s'\n", local_name, connect_to);
@@ -767,7 +838,7 @@
                 l->transport = transport;
                 add_transport_disconnect(l->transport, &l->disconnect);
             }
-            return 0;
+            return INSTALL_STATUS_OK;
         }
     }
 
@@ -804,11 +875,11 @@
         l->disconnect.func   = listener_disconnect;
         add_transport_disconnect(transport, &l->disconnect);
     }
-    return 0;
+    return INSTALL_STATUS_OK;
 
 nomem:
     fatal("cannot allocate listener");
-    return 0;
+    return INSTALL_STATUS_INTERNAL_ERROR;
 }
 
 #ifdef HAVE_WIN32_PROC
@@ -1113,7 +1184,7 @@
 
     char local_name[30];
     build_local_name(local_name, sizeof(local_name), server_port);
-    if(install_listener(local_name, "*smartsocket*", NULL)) {
+    if(install_listener(local_name, "*smartsocket*", NULL, 0)) {
         exit(1);
     }
 #else
@@ -1180,7 +1251,7 @@
     } else {
         char local_name[30];
         build_local_name(local_name, sizeof(local_name), server_port);
-        if(install_listener(local_name, "*smartsocket*", NULL)) {
+        if(install_listener(local_name, "*smartsocket*", NULL, 0)) {
             exit(1);
         }
     }
@@ -1474,24 +1545,63 @@
     }
 #endif // ADB_HOST
 
-    if(!strncmp(service,"forward:",8) || !strncmp(service,"killforward:",12)) {
+    if(!strcmp(service,"list-forward")) {
+        // Create the list of forward redirections.
+        char header[9];
+        int buffer_size = format_listeners(NULL, 0);
+        // Add one byte for the trailing zero.
+        char* buffer = malloc(buffer_size+1);
+        (void) format_listeners(buffer, buffer_size+1);
+        snprintf(header, sizeof header, "OKAY%04x", buffer_size);
+        writex(reply_fd, header, 8);
+        writex(reply_fd, buffer, buffer_size);
+        free(buffer);
+        return 0;
+    }
+
+    if (!strcmp(service,"killforward-all")) {
+        remove_all_listeners();
+        adb_write(reply_fd, "OKAYOKAY", 8);
+        return 0;
+    }
+
+    if(!strncmp(service,"forward:",8) ||
+       !strncmp(service,"killforward:",12)) {
         char *local, *remote, *err;
         int r;
         atransport *transport;
 
         int createForward = strncmp(service,"kill",4);
+        int no_rebind = 0;
 
-        local = service + (createForward ? 8 : 12);
-        remote = strchr(local,';');
-        if(remote == 0) {
-            sendfailmsg(reply_fd, "malformed forward spec");
-            return 0;
+        local = strchr(service, ':') + 1;
+
+        // Handle forward:norebind:<local>... here
+        if (createForward && !strncmp(local, "norebind:", 9)) {
+            no_rebind = 1;
+            local = strchr(local, ':') + 1;
         }
 
-        *remote++ = 0;
-        if((local[0] == 0) || (remote[0] == 0) || (remote[0] == '*')){
-            sendfailmsg(reply_fd, "malformed forward spec");
-            return 0;
+        remote = strchr(local,';');
+
+        if (createForward) {
+            // Check forward: parameter format: '<local>;<remote>'
+            if(remote == 0) {
+                sendfailmsg(reply_fd, "malformed forward spec");
+                return 0;
+            }
+
+            *remote++ = 0;
+            if((local[0] == 0) || (remote[0] == 0) || (remote[0] == '*')){
+                sendfailmsg(reply_fd, "malformed forward spec");
+                return 0;
+            }
+        } else {
+            // Check killforward: parameter format: '<local>'
+            if (local[0] == 0) {
+                sendfailmsg(reply_fd, "malformed forward spec");
+                return 0;
+            }
         }
 
         transport = acquire_one_transport(CS_ANY, ttype, serial, &err);
@@ -1501,9 +1611,9 @@
         }
 
         if (createForward) {
-            r = install_listener(local, remote, transport);
+            r = install_listener(local, remote, transport, no_rebind);
         } else {
-            r = remove_listener(local, remote, transport);
+            r = remove_listener(local, transport);
         }
         if(r == 0) {
                 /* 1st OKAY is connect, 2nd OKAY is status */
@@ -1512,7 +1622,18 @@
         }
 
         if (createForward) {
-            sendfailmsg(reply_fd, (r == -1) ? "cannot rebind smartsocket" : "cannot bind socket");
+            const char* message;
+            switch (r) {
+              case INSTALL_STATUS_CANNOT_BIND:
+                message = "cannot bind to socket";
+                break;
+              case INSTALL_STATUS_CANNOT_REBIND:
+                message = "cannot rebind existing socket";
+                break;
+              default:
+                message = "internal error";
+            }
+            sendfailmsg(reply_fd, message);
         } else {
             sendfailmsg(reply_fd, "cannot remove listener");
         }