Merge "adb: add help text for USB permission errors." am: aafa096090 am: e37325defc
am: 1effd452ab

* commit '1effd452ab94b290d6756dced8c0ddb918d81561':
  adb: add help text for USB permission errors.
diff --git a/adb.h b/adb.h
index be29f29..774215e 100644
--- a/adb.h
+++ b/adb.h
@@ -214,18 +214,23 @@
 void local_connect(int port);
 int  local_connect_arbitrary_ports(int console_port, int adb_port, std::string* error);
 
-/* usb host/client interface */
+// USB host/client interface.
 void usb_init();
 int usb_write(usb_handle *h, const void *data, int len);
 int usb_read(usb_handle *h, void *data, int len);
 int usb_close(usb_handle *h);
 void usb_kick(usb_handle *h);
 
-/* used for USB device detection */
+// USB device detection.
 #if ADB_HOST
 int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol);
 #endif
 
+// USB permission error help text. The short version will be one line, long may be multi-line.
+// Returns a string message to print, or an empty string if no problems could be found.
+std::string UsbNoPermissionsShortHelpText();
+std::string UsbNoPermissionsLongHelpText();
+
 int adb_commandline(int argc, const char **argv);
 
 ConnectionState connection_state(atransport *t);
diff --git a/transport.cpp b/transport.cpp
index b32c86a..988007d 100644
--- a/transport.cpp
+++ b/transport.cpp
@@ -674,7 +674,11 @@
     adb_mutex_lock(&transport_lock);
     for (const auto& t : transport_list) {
         if (t->connection_state == kCsNoPerm) {
-            *error_out = "insufficient permissions for device";
+            *error_out = UsbNoPermissionsLongHelpText();
+            // If we couldn't figure out a reasonable help message default to something generic.
+            if (error_out->empty()) {
+                *error_out = "insufficient permissions for device";
+            }
             continue;
         }
 
@@ -748,17 +752,20 @@
     return result;
 }
 
-const char* atransport::connection_state_name() const {
+const std::string atransport::connection_state_name() const {
     switch (connection_state) {
-    case kCsOffline: return "offline";
-    case kCsBootloader: return "bootloader";
-    case kCsDevice: return "device";
-    case kCsHost: return "host";
-    case kCsRecovery: return "recovery";
-    case kCsNoPerm: return "no permissions";
-    case kCsSideload: return "sideload";
-    case kCsUnauthorized: return "unauthorized";
-    default: return "unknown";
+        case kCsOffline: return "offline";
+        case kCsBootloader: return "bootloader";
+        case kCsDevice: return "device";
+        case kCsHost: return "host";
+        case kCsRecovery: return "recovery";
+        case kCsNoPerm: {
+            std::string message = UsbNoPermissionsShortHelpText();
+            return message.empty() ? "no permissions" : message;
+        }
+        case kCsSideload: return "sideload";
+        case kCsUnauthorized: return "unauthorized";
+        default: return "unknown";
     }
 }
 
@@ -864,7 +871,8 @@
         *result += '\t';
         *result += t->connection_state_name();
     } else {
-        android::base::StringAppendF(result, "%-22s %s", serial, t->connection_state_name());
+        android::base::StringAppendF(result, "%-22s %s", serial,
+                                     t->connection_state_name().c_str());
 
         append_transport_info(result, "", t->devpath, false);
         append_transport_info(result, "product:", t->product, false);
diff --git a/transport.h b/transport.h
index d9845b6..76d6afa 100644
--- a/transport.h
+++ b/transport.h
@@ -90,7 +90,7 @@
     fdevent auth_fde;
     size_t failed_auth_attempts = 0;
 
-    const char* connection_state_name() const;
+    const std::string connection_state_name() const;
 
     void update_version(int version, size_t payload);
     int get_protocol_version() const;
diff --git a/usb_linux.cpp b/usb_linux.cpp
index ed5d2d6..e887a94 100644
--- a/usb_linux.cpp
+++ b/usb_linux.cpp
@@ -22,6 +22,7 @@
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <grp.h>
 #include <linux/usb/ch9.h>
 #include <linux/usbdevice_fs.h>
 #include <linux/version.h>
@@ -595,3 +596,54 @@
         fatal_errno("cannot create device_poll thread");
     }
 }
+
+static const char kPermissionsHelpUrl[] = "developer.android.com/tools/device.html";
+
+// Returns a message describing any potential problems we find with udev, or nullptr if we can't
+// find plugdev information (i.e. udev is not installed).
+static const char* GetUdevProblem() {
+    errno = 0;
+    group* plugdev_group = getgrnam("plugdev");
+
+    if (plugdev_group == nullptr) {
+        if (errno != 0) {
+            D("failed to read plugdev group info: %s", strerror(errno));
+        }
+        // We can't give any generally useful advice here, just let the caller print the help URL.
+        return nullptr;
+    }
+
+    // getgroups(2) indicates that the group_member() may not check the egid so we check it
+    // additionally just to be sure.
+    if (group_member(plugdev_group->gr_gid) || getegid() == plugdev_group->gr_gid) {
+        // The user is in plugdev so the problem is likely with the udev rules.
+        return "verify udev rules";
+    }
+    return "udev requires plugdev group membership";
+}
+
+// Short help text must be a single line, and will look something like:
+//   no permissions (reason); see <URL>
+std::string UsbNoPermissionsShortHelpText() {
+    std::string help_text = "no permissions";
+
+    const char* problem = GetUdevProblem();
+    if (problem != nullptr) {
+        help_text += android::base::StringPrintf(" (%s)", problem);
+    }
+
+    return android::base::StringPrintf("%s; see [%s]", help_text.c_str(), kPermissionsHelpUrl);
+}
+
+// Long help text can span multiple lines and should provide more detailed information.
+std::string UsbNoPermissionsLongHelpText() {
+    std::string header = "USB permission failure";
+
+    const char* problem = GetUdevProblem();
+    if (problem != nullptr) {
+        header += android::base::StringPrintf(": %s", problem);
+    }
+
+    return android::base::StringPrintf("%s.\nSee [%s] for more information.",
+                                       header.c_str(), kPermissionsHelpUrl);
+}
diff --git a/usb_linux_client.cpp b/usb_linux_client.cpp
index ceed8fa..c5e7452 100644
--- a/usb_linux_client.cpp
+++ b/usb_linux_client.cpp
@@ -571,3 +571,12 @@
 {
     h->kick(h);
 }
+
+// kCsNoPerm is a host-side issue, we can just ignore it here.
+std::string UsbNoPermissionsShortHelpText() {
+    return "";
+}
+
+std::string UsbNoPermissionsLongHelpText() {
+    return "";
+}
diff --git a/usb_osx.cpp b/usb_osx.cpp
index 148be1d..f494795 100644
--- a/usb_osx.cpp
+++ b/usb_osx.cpp
@@ -552,3 +552,12 @@
         handle->interface = 0;
     }
 }
+
+// kCsNoPerm is Linux-only.
+std::string UsbNoPermissionsShortHelpText() {
+    return "";
+}
+
+std::string UsbNoPermissionsLongHelpText() {
+    return "";
+}
diff --git a/usb_windows.cpp b/usb_windows.cpp
index 8d3501e..9124685 100644
--- a/usb_windows.cpp
+++ b/usb_windows.cpp
@@ -659,3 +659,12 @@
   }
   adb_mutex_unlock(&usb_lock);
 }
+
+// kCsNoPerm is Linux-only.
+std::string UsbNoPermissionsShortHelpText() {
+    return "";
+}
+
+std::string UsbNoPermissionsLongHelpText() {
+    return "";
+}