adb: Generalizing -s to take qualifiers.

Prior to this change, -s could take either a serial number or a
device path (e.g. "-s 01498B1F02015015" or "-s usb:1-4.2").  This
change extends -s to also allow product, model or device names
(e.g. "-s product:mysid").  These new qualifiers will only be
available on devices that are running an adb daemon that provides
properties in the connect message per Change-Id:
    I09200decde4facb8fc9b4056fdae910155f2bcb9

The product, model and device are derived from the
ro.product.name, ro.product.model and ro.product.device
properties respectively.  They are prefixed with "product:",
"model:" or "device:" as appropriate.  In addition, any
non-alphanumerics in the model are changed to underscores.

If the -s parameter matches multiple devices, the result will be
the same as when multiple devices are connected but no -d, -e or
-s option is specified.  In general, this means the user will get
"error: more than one device".  However for get-state,
get-devpath and get-serialno, they will get "unknown".

The format of "devices -l" was changed to list all of the
qualifiers that are available.  The following example output
(with the last digits of the serial numbers replaced with X's) is
with a Galaxy Prime with an older adb daemon and another Galaxy
Prime and Galaxy S both with the enhanced adb daemons:

List of devices attached
016B75D60A0060XX       device usb:2-5 product:mysid model:Galaxy_Nexus device:toro
3731B535FAC200XX       device usb:1-4.2 product:soju model:Nexus_S device:crespo
01498B1F020150XX       device usb:1-4.1

Note that the serial number and state are now column oriented
instead of tab delimited.  After the serial number and state, all
qualifiers are listed with each preceded by a space.  The output
of the original devices command (without -l) is unchanged.

Change-Id: Iceeb2789874effc25a630d514a375d6f1889dc56
Signed-off-by: Scott Anderson <saa@android.com>
diff --git a/adb/adb.c b/adb/adb.c
index ebf7a7e..ed8d230 100644
--- a/adb/adb.c
+++ b/adb/adb.c
@@ -289,9 +289,7 @@
 
 /* qual_overwrite is used to overwrite a qualifier string.  dst is a
  * pointer to a char pointer.  It is assumed that if *dst is non-NULL, it
- * was malloc'ed and needs to freed.  A char buffer will be malloc'ed and
- * filled with src and *dst will be set to
- * point to the buffer.
+ * was malloc'ed and needs to freed.  *dst will be set to a dup of src.
  */
 static void qual_overwrite(char **dst, const char *src)
 {
@@ -311,7 +309,8 @@
 {
     static const char *prop_seps = ";";
     static const char key_val_sep = '=';
-    char *cp, *type;
+    char *cp;
+    char *type;
 
     D("parse_banner: %s\n", banner);
     type = banner;
diff --git a/adb/adb.h b/adb/adb.h
index e095d70..300882c 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -251,7 +251,7 @@
 ** get_device_transport does an acquire on your behalf before returning
 */
 void init_transport_registration(void);
-int  list_transports(char *buf, size_t  bufsize, int show_devpath);
+int  list_transports(char *buf, size_t  bufsize, int long_listing);
 void update_transports(void);
 
 asocket*  create_device_tracker(void);
diff --git a/adb/commandline.c b/adb/commandline.c
index 2de0516..22c878b 100644
--- a/adb/commandline.c
+++ b/adb/commandline.c
@@ -85,7 +85,7 @@
         " -e                            - directs command to the only running emulator.\n"
         "                                 returns an error if more than one emulator is running.\n"
         " -s <specific device>          - directs command to the device or emulator with the given\n"
-        "                                 serial number or device path. Overrides ANDROID_SERIAL\n"
+        "                                 serial number or qualifier. Overrides ANDROID_SERIAL\n"
         "                                 environment variable.\n"
         " -p <product name or path>     - simple product name like 'sooner', or\n"
         "                                 a relative/absolute path to a product\n"
@@ -94,7 +94,7 @@
         "                                 environment variable is used, which must\n"
         "                                 be an absolute path.\n"
         " devices [-l]                  - list all connected devices\n"
-        "                                 ('-l' means list device paths)\n"
+        "                                 ('-l' will also list device qualifiers)\n"
         " connect <host>[:<port>]       - connect to a device via TCP/IP\n"
         "                                 Port 5555 is used by default if no port number is specified.\n"
         " disconnect [<host>[:<port>]]  - disconnect from a TCP/IP device.\n"
diff --git a/adb/sockets.c b/adb/sockets.c
index a73fc62..830cf26 100644
--- a/adb/sockets.c
+++ b/adb/sockets.c
@@ -591,15 +591,29 @@
     return n;
 }
 
+#define PREFIX(str) { str, sizeof(str) - 1 }
+static const struct prefix_struct {
+    const char *str;
+    const size_t len;
+} prefixes[] = {
+    PREFIX("usb:"),
+    PREFIX("product:"),
+    PREFIX("model:"),
+    PREFIX("device:"),
+};
+static const int num_prefixes = (sizeof(prefixes) / sizeof(prefixes[0]));
+
 /* skip_host_serial return the position in a string
    skipping over the 'serial' parameter in the ADB protocol,
    where parameter string may be a host:port string containing
    the protocol delimiter (colon). */
 char *skip_host_serial(char *service) {
     char *first_colon, *serial_end;
+    int i;
 
-    if (!strncmp(service, "usb:", 4)) {
-        return strchr(service + 4, ':');
+    for (i = 0; i < num_prefixes; i++) {
+        if (!strncmp(service, prefixes[i].str, prefixes[i].len))
+            return strchr(service + prefixes[i].len, ':');
     }
 
     first_colon = strchr(service, ':');
diff --git a/adb/transport.c b/adb/transport.c
index 359b0d6..c88b90f 100644
--- a/adb/transport.c
+++ b/adb/transport.c
@@ -743,6 +743,45 @@
     dis->next = dis->prev = dis;
 }
 
+static int qual_char_is_invalid(char ch)
+{
+    if ('A' <= ch && ch <= 'Z')
+        return 0;
+    if ('a' <= ch && ch <= 'z')
+        return 0;
+    if ('0' <= ch && ch <= '9')
+        return 0;
+    return 1;
+}
+
+static int qual_match(const char *to_test,
+                      const char *prefix, const char *qual, int sanitize_qual)
+{
+    if (!to_test || !*to_test)
+        /* Return true if both the qual and to_test are null strings. */
+        return !qual || !*qual;
+
+    if (!qual)
+        return 0;
+
+    if (prefix) {
+        while (*prefix) {
+            if (*prefix++ != *to_test++)
+                return 0;
+        }
+    }
+
+    while (*qual) {
+        char ch = *qual++;
+        if (sanitize_qual && qual_char_is_invalid(ch))
+            ch = '_';
+        if (ch != *to_test++)
+            return 0;
+    }
+
+    /* Everything matched so far.  Return true if *to_test is a NUL. */
+    return !*to_test;
+}
 
 atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char** error_out)
 {
@@ -764,13 +803,19 @@
 
         /* check for matching serial number */
         if (serial) {
-            if (t->serial && !strcmp(serial, t->serial)) {
+            if ((t->serial && !strcmp(serial, t->serial)) ||
+                (t->devpath && !strcmp(serial, t->devpath)) ||
+                qual_match(serial, "product:", t->product, 0) ||
+                qual_match(serial, "model:", t->model, 1) ||
+                qual_match(serial, "device:", t->device, 0)) {
+                if (result) {
+                    if (error_out)
+                        *error_out = "more than one device";
+                    ambiguous = 1;
+                    result = NULL;
+                    break;
+                }
                 result = t;
-                break;
-            }
-            if (t->devpath && !strcmp(serial, t->devpath)) {
-                result = t;
-                break;
             }
         } else {
             if (ttype == kTransportUsb && t->type == kTransportUsb) {
@@ -846,7 +891,58 @@
     }
 }
 
-int list_transports(char *buf, size_t  bufsize, int show_devpath)
+static void add_qual(char **buf, size_t *buf_size,
+                     const char *prefix, const char *qual, int sanitize_qual)
+{
+    size_t len;
+    int prefix_len;
+
+    if (!buf || !*buf || !buf_size || !*buf_size || !qual || !*qual)
+        return;
+
+    len = snprintf(*buf, *buf_size, "%s%n%s", prefix, &prefix_len, qual);
+
+    if (sanitize_qual) {
+        char *cp;
+        for (cp = *buf + prefix_len; cp < *buf + len; cp++) {
+            if (qual_char_is_invalid(*cp))
+                *cp = '_';
+        }
+    }
+
+    *buf_size -= len;
+    *buf += len;
+}
+
+static size_t format_transport(atransport *t, char *buf, size_t bufsize,
+                               int long_listing)
+{
+    const char* serial = t->serial;
+    if (!serial || !serial[0])
+        serial = "????????????";
+
+    if (!long_listing) {
+        return snprintf(buf, bufsize, "%s\t%s\n", serial, statename(t));
+    } else {
+        size_t len, remaining = bufsize;
+
+        len = snprintf(buf, remaining, "%-22s %s", serial, statename(t));
+        remaining -= len;
+        buf += len;
+
+        add_qual(&buf, &remaining, " ", t->devpath, 0);
+        add_qual(&buf, &remaining, " product:", t->product, 0);
+        add_qual(&buf, &remaining, " model:", t->model, 1);
+        add_qual(&buf, &remaining, " device:", t->device, 0);
+
+        len = snprintf(buf, remaining, "\n");
+        remaining -= len;
+
+        return bufsize - remaining;
+    }
+}
+
+int list_transports(char *buf, size_t  bufsize, int long_listing)
 {
     char*       p   = buf;
     char*       end = buf + bufsize;
@@ -856,17 +952,7 @@
         /* XXX OVERRUN PROBLEMS XXX */
     adb_mutex_lock(&transport_lock);
     for(t = transport_list.next; t != &transport_list; t = t->next) {
-        const char* serial = t->serial;
-        if (!serial || !serial[0])
-            serial = "????????????";
-        if (show_devpath) {
-            const char* devpath = t->devpath;
-            if (!devpath || !devpath[0])
-                devpath = "????????????";
-            len = snprintf(p, end - p, "%s\t%s\t%s\n", serial, devpath, statename(t));
-        } else
-            len = snprintf(p, end - p, "%s\t%s\n", serial, statename(t));
-
+        len = format_transport(t, p, end - p, long_listing);
         if (p + len >= end) {
             /* discard last line if buffer is too short */
             break;