darwin: use the IO registry to detect if a kernel driver is attached to an interface

The implementation of libusb_kernel_driver_active was attempting to open the interface to
check if a driver is attached. This may have side effects (like configuring the device)
that may be unexpected to the user. This commit updates the code to find the interface's
IO registry entry (either IOUSBHostInterface or the legacy IOUSBInterface) and check if
the entry has a child entry. A child entry indicates that a driver is currently attached.

Signed-off-by: Nathan Hjelm <hjelmn@google.com>
diff --git a/configure.ac b/configure.ac
index 5899fc2..4302b53 100644
--- a/configure.ac
+++ b/configure.ac
@@ -162,6 +162,7 @@
 darwin)
 	AC_CHECK_FUNCS([pthread_threadid_np])
 	LIBS="${LIBS} -lobjc -Wl,-framework,IOKit -Wl,-framework,CoreFoundation -Wl,-framework,Security"
+	AC_CHECK_HEADERS([IOKit/usb/IOUSBHostFamilyDefinitions.h])
 	;;
 haiku)
 	LIBS="${LIBS} -lbe"
diff --git a/libusb/os/darwin_usb.c b/libusb/os/darwin_usb.c
index afa13c2..85ca1e8 100644
--- a/libusb/os/darwin_usb.c
+++ b/libusb/os/darwin_usb.c
@@ -1815,12 +1815,49 @@
   }
 }
 
+static io_service_t usb_find_interface_matching_location (const io_name_t class_name, UInt8 interface_number, UInt32 location) {
+  CFMutableDictionaryRef matchingDict = IOServiceMatching (class_name);
+  CFMutableDictionaryRef propertyMatchDict = CFDictionaryCreateMutable (kCFAllocatorDefault, 0,
+                                                                        &kCFTypeDictionaryKeyCallBacks,
+                                                                        &kCFTypeDictionaryValueCallBacks);
+  CFTypeRef locationCF = CFNumberCreate (NULL, kCFNumberSInt32Type, &location);
+  CFTypeRef interfaceCF =  CFNumberCreate (NULL, kCFNumberSInt8Type, &interface_number);
+
+  CFDictionarySetValue (matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict);
+  CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBDevicePropertyLocationID), locationCF);
+  CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBHostMatchingPropertyInterfaceNumber), interfaceCF);
+
+  CFRelease (interfaceCF);
+  CFRelease (locationCF);
+  CFRelease (propertyMatchDict);
+
+  return IOServiceGetMatchingService (kIOMasterPortDefault, matchingDict);
+}
+
 static int darwin_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t interface) {
-  enum libusb_error ret = darwin_claim_interface (dev_handle, interface);
-  if (ret == LIBUSB_SUCCESS) {
-    darwin_release_interface (dev_handle, interface);
+  struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
+  io_service_t usb_interface, child = IO_OBJECT_NULL;
+
+  /* locate the IO registry entry for this interface */
+  usb_interface = usb_find_interface_matching_location (kIOUSBHostInterfaceClassName, interface, dpriv->location);
+  if (0 == usb_interface) {
+    /* check for the legacy class entry */
+    usb_interface = usb_find_interface_matching_location (kIOUSBInterfaceClassName, interface, dpriv->location);
+    if (0 == usb_interface) {
+      return LIBUSB_ERROR_NOT_FOUND;
+    }
   }
-  return (ret == LIBUSB_ERROR_ACCESS);
+
+  /* if the IO object has a child entry in the IO Registry it has a kernel driver attached */
+  (void) IORegistryEntryGetChildEntry (usb_interface, kIOServicePlane, &child);
+  IOObjectRelease (usb_interface);
+  if (IO_OBJECT_NULL != child) {
+    IOObjectRelease (child);
+    return 1;
+  }
+
+  /* no driver */
+  return 0;
 }
 
 static void darwin_destroy_device(struct libusb_device *dev) {
diff --git a/libusb/os/darwin_usb.h b/libusb/os/darwin_usb.h
index 9021266..7b72fff 100644
--- a/libusb/os/darwin_usb.h
+++ b/libusb/os/darwin_usb.h
@@ -30,6 +30,10 @@
 #include <IOKit/usb/IOUSBLib.h>
 #include <IOKit/IOCFPlugIn.h>
 
+#if defined(HAVE_IOKIT_USB_IOUSBHOSTFAMILYDEFINITIONS_H)
+#include <IOKit/usb/IOUSBHostFamilyDefinitions.h>
+#endif
+
 /* IOUSBInterfaceInferface */
 
 /* New in OS 10.12.0. */
@@ -144,6 +148,14 @@
 
 #endif
 
+#if !defined(kIOUSBHostInterfaceClassName)
+#define kIOUSBHostInterfaceClassName "IOUSBHostInterface"
+#endif
+
+#if !defined(kUSBHostMatchingPropertyInterfaceNumber)
+#define kUSBHostMatchingPropertyInterfaceNumber "bInterfaceNumber"
+#endif
+
 #if !defined(IO_OBJECT_NULL)
 #define IO_OBJECT_NULL ((io_object_t) 0)
 #endif
diff --git a/libusb/version_nano.h b/libusb/version_nano.h
index 374ee65..8dd74ce 100644
--- a/libusb/version_nano.h
+++ b/libusb/version_nano.h
@@ -1 +1 @@
-#define LIBUSB_NANO 11642
+#define LIBUSB_NANO 11643