From 0662b6460e6533b1d216eb0a15086df056ea2cd6 Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Wed, 11 Sep 2019 22:06:12 -0700 Subject: [PATCH] Add IOKitUtil class --- .../sun/jna/platform/mac/CoreFoundation.java | 4 + .../src/com/sun/jna/platform/mac/IOKit.java | 4 +- .../com/sun/jna/platform/mac/IOKitUtil.java | 346 ++++++++++++++++++ .../jna/platform/mac/DiskArbitrationTest.java | 10 + .../com/sun/jna/platform/mac/IOKitTest.java | 39 +- 5 files changed, 376 insertions(+), 27 deletions(-) create mode 100644 contrib/platform/src/com/sun/jna/platform/mac/IOKitUtil.java diff --git a/contrib/platform/src/com/sun/jna/platform/mac/CoreFoundation.java b/contrib/platform/src/com/sun/jna/platform/mac/CoreFoundation.java index 3b807fe095..f8a77adb6a 100644 --- a/contrib/platform/src/com/sun/jna/platform/mac/CoreFoundation.java +++ b/contrib/platform/src/com/sun/jna/platform/mac/CoreFoundation.java @@ -377,6 +377,10 @@ public String stringValue() { class CFIndex extends NativeLong { private static final long serialVersionUID = 1L; + public CFIndex() { + super(); + } + public CFIndex(long value) { super(value); } diff --git a/contrib/platform/src/com/sun/jna/platform/mac/IOKit.java b/contrib/platform/src/com/sun/jna/platform/mac/IOKit.java index 483720c2f1..7b079d0d27 100644 --- a/contrib/platform/src/com/sun/jna/platform/mac/IOKit.java +++ b/contrib/platform/src/com/sun/jna/platform/mac/IOKit.java @@ -257,7 +257,7 @@ public IOConnect(Pointer p) { *

* The service must be released by the caller. */ - IOService IOServiceGetMatchingService(MachPort masterPort, CFMutableDictionaryRef matchingDictionary); + IOService IOServiceGetMatchingService(MachPort masterPort, CFDictionaryRef matchingDictionary); /** * Look up registered IOService objects that match a matching dictionary. @@ -275,7 +275,7 @@ public IOConnect(Pointer p) { * by the caller when the iteration is finished. * @return 0 if successful, otherwise a {@code kern_return_t} error code. */ - int IOServiceGetMatchingServices(MachPort masterPort, CFMutableDictionaryRef matchingDictionary, + int IOServiceGetMatchingServices(MachPort masterPort, CFDictionaryRef matchingDictionary, PointerByReference iterator); /** diff --git a/contrib/platform/src/com/sun/jna/platform/mac/IOKitUtil.java b/contrib/platform/src/com/sun/jna/platform/mac/IOKitUtil.java new file mode 100644 index 0000000000..34be4914ce --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/mac/IOKitUtil.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2019 Daniel Widdis + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna.platform.mac; + +import com.sun.jna.Pointer; +import com.sun.jna.platform.mac.CoreFoundation.CFBooleanRef; +import com.sun.jna.platform.mac.CoreFoundation.CFDataRef; +import com.sun.jna.platform.mac.CoreFoundation.CFDictionaryRef; +import com.sun.jna.platform.mac.CoreFoundation.CFMutableDictionaryRef; +import com.sun.jna.platform.mac.CoreFoundation.CFNumberRef; +import com.sun.jna.platform.mac.CoreFoundation.CFStringRef; +import com.sun.jna.platform.mac.CoreFoundation.CFTypeRef; +import com.sun.jna.platform.mac.IOKit.IOIterator; +import com.sun.jna.platform.mac.IOKit.IORegistryEntry; +import com.sun.jna.platform.mac.IOKit.IOService; +import com.sun.jna.platform.mac.IOKit.MachPort; +import com.sun.jna.ptr.PointerByReference; + +/** + * Provides utilities for IOKit. + */ +public class IOKitUtil { + private static final IOKit IO = IOKit.INSTANCE; + private static final CoreFoundation CF = CoreFoundation.INSTANCE; + + private IOKitUtil() { + } + + /** + * Gets a pointer to the Mach Master Port. + * + * @return The master port. + *

+ * Multiple calls to {@link #getMasterPort} will not result in leaking + * ports (each call to {@link IOKit#IOMasterPort} adds another send + * right to the port) but it is considered good programming practice to + * deallocate the port when you are finished with it, using + * {@link IOKit#IOObjectRelease}. + */ + public static MachPort getMasterPort() { + PointerByReference port = new PointerByReference(); + IO.IOMasterPort(IOKit.MACH_PORT_NULL, port); + return new MachPort(port.getValue()); + } + + /** + * Gets the IO Registry root. + * + * @return a handle to the IORoot. Callers should release when finished, using + * {@link IOKit#IOObjectRelease}. + */ + public static IORegistryEntry getRoot() { + MachPort masterPort = getMasterPort(); + IORegistryEntry root = IO.IORegistryGetRootEntry(masterPort); + masterPort.release(); + return root; + } + + /** + * Opens a the first IOService matching a service name. + * + * @param serviceName + * The service name to match + * @return a handle to an IOService if successful, {@code null} if failed. + * Callers should release when finished, using + * {@link IOKit#IOObjectRelease}. + */ + public static IOService getMatchingService(String serviceName) { + CFMutableDictionaryRef dict = IO.IOServiceMatching(serviceName); + if (dict != null) { + return getMatchingService(dict); + } + return null; + } + + /** + * Opens a the first IOService matching a dictionary. + * + * @param matchingDictionary + * The dictionary to match. This method will consume a reference to + * the dictionary. + * @return a handle to an IOService if successful, {@code null} if failed. + * Callers should release when finished, using + * {@link IOKit#IOObjectRelease}. + */ + public static IOService getMatchingService(CFDictionaryRef matchingDictionary) { + MachPort masterPort = getMasterPort(); + IOService service = IO.IOServiceGetMatchingService(masterPort, matchingDictionary); + masterPort.release(); + return service; + } + + /** + * Convenience method to get IOService objects matching a service name. + * + * @param serviceName + * The service name to match + * @return a handle to an IOIterator if successful, {@code null} if failed. + * Callers should release when finished, using + * {@link IOKit#IOObjectRelease}. + */ + public static IOIterator getMatchingServices(String serviceName) { + CFMutableDictionaryRef dict = IO.IOServiceMatching(serviceName); + if (dict != null) { + return getMatchingServices(dict); + } + return null; + } + + /** + * Convenience method to get IOService objects matching a dictionary. + * + * @param matchingDictionary + * The dictionary to match. This method will consume a reference to + * the dictionary. + * @return a handle to an IOIterator if successful, {@code null} if failed. + * Callers should release when finished, using + * {@link IOKit#IOObjectRelease}. + */ + public static IOIterator getMatchingServices(CFDictionaryRef matchingDictionary) { + MachPort masterPort = getMasterPort(); + PointerByReference serviceIterator = new PointerByReference(); + int result = IO.IOServiceGetMatchingServices(masterPort, matchingDictionary, serviceIterator); + masterPort.release(); + if (result == 0 && serviceIterator.getValue() != null) { + return new IOIterator(serviceIterator.getValue()); + } + return null; + } + + /** + * Convenience method to get the IO dictionary matching a bsd name. + * + * @param bsdName + * The bsd name of the registry entry + * @return The dictionary ref if successful, {@code null} if failed. Callers + * should release when finished, using {@link IOKit#IOObjectRelease}. + */ + public static CFMutableDictionaryRef getBSDNameMatchingDict(String bsdName) { + MachPort masterPort = getMasterPort(); + CFMutableDictionaryRef result = IO.IOBSDNameMatching(masterPort, 0, bsdName); + masterPort.release(); + return result; + } + + /** + * Convenience method to get a String value from an IO Registry + * + * @param entry + * A handle to the registry entry + * @param key + * The string name of the key to retrieve + * @return The value of the registry entry if it exists;{@code null} otherwise + */ + public static String getIORegistryStringProperty(IORegistryEntry entry, String key) { + String value = null; + CFStringRef keyAsCFString = CFStringRef.createCFString(key); + CFTypeRef valueAsCFType = IO.IORegistryEntryCreateCFProperty(entry, keyAsCFString, CF.CFAllocatorGetDefault(), + 0); + if (valueAsCFType != null && valueAsCFType.getPointer() != null) { + CFStringRef valueAsCFString = new CFStringRef(valueAsCFType.getPointer()); + value = valueAsCFString.stringValue(); + } + keyAsCFString.release(); + if (valueAsCFType != null) { + valueAsCFType.release(); + } + return value; + } + + /** + * Convenience method to get a {@code long} value from an IO Registry. + * + * @param entry + * A handle to the registry entry + * @param key + * The string name of the key to retrieve + * @param defaultValue + * The value to return if unsuccessful + * @return The value of the registry entry if it exists. + *

+ * This method assumes a 64-bit integer is stored and does not do type + * checking. If this object's type differs from the return type, and the + * conversion is lossy or the return value is out of range, then this + * method returns an approximate value. If the value does not exist, the + * {@code defaultValue} is returned. + */ + public static long getIORegistryLongProperty(IORegistryEntry entry, String key, long defaultValue) { + long value = defaultValue; + CFStringRef keyAsCFString = CFStringRef.createCFString(key); + CFTypeRef valueAsCFType = IO.IORegistryEntryCreateCFProperty(entry, keyAsCFString, CF.CFAllocatorGetDefault(), + 0); + if (valueAsCFType != null && valueAsCFType.getPointer() != null) { + CFNumberRef valueAsCFNumber = new CFNumberRef(valueAsCFType.getPointer()); + value = valueAsCFNumber.longValue(); + } + keyAsCFString.release(); + if (valueAsCFType != null) { + valueAsCFType.release(); + } + return value; + } + + /** + * Convenience method to get an {@code int} value from an IO Registry. + * + * @param entry + * A handle to the registry entry + * @param key + * The string name of the key to retrieve + * @param defaultValue + * The value to return if unsuccessful + * @return The value of the registry entry if it exists. + *

+ * This method assumes a 32-bit integer is stored and does not do type + * checking. If this object's type differs from the return type, and the + * conversion is lossy or the return value is out of range, then this + * method returns an approximate value. If the value does not exist, the + * {@code defaultValue} is returned. + */ + public static int getIORegistryIntProperty(IORegistryEntry entry, String key, int defaultValue) { + int value = defaultValue; + CFStringRef keyAsCFString = CFStringRef.createCFString(key); + CFTypeRef valueAsCFType = IO.IORegistryEntryCreateCFProperty(entry, keyAsCFString, CF.CFAllocatorGetDefault(), + 0); + if (valueAsCFType != null) { + CFNumberRef valueAsCFNumber = new CFNumberRef(valueAsCFType.getPointer()); + value = valueAsCFNumber.intValue(); + } + keyAsCFString.release(); + if (valueAsCFType != null) { + valueAsCFType.release(); + } + return value; + } + + /** + * Convenience method to get a {@code double} value from an IO Registry. + * + * @param entry + * A handle to the registry entry + * @param key + * The string name of the key to retrieve + * @param defaultValue + * The value to return if unsuccessful + * @return The value of the registry entry if it exists. + *

+ * This method assumes a floating point value is stored and does not do + * type checking. If this object's type differs from the return type, + * and the conversion is lossy or the return value is out of range, then + * this method returns an approximate value. If the value does not + * exist, the {@code defaultValue} is returned. + */ + public static double getIORegistryIntProperty(IORegistryEntry entry, String key, double defaultValue) { + double value = defaultValue; + CFStringRef keyAsCFString = CFStringRef.createCFString(key); + CFTypeRef valueAsCFType = IO.IORegistryEntryCreateCFProperty(entry, keyAsCFString, CF.CFAllocatorGetDefault(), + 0); + if (valueAsCFType != null) { + CFNumberRef valueAsCFNumber = new CFNumberRef(valueAsCFType.getPointer()); + value = valueAsCFNumber.doubleValue(); + } + keyAsCFString.release(); + if (valueAsCFType != null) { + valueAsCFType.release(); + } + return value; + } + + /** + * Convenience method to get a Boolean value from an IO Registry. + * + * @param entry + * A handle to the registry entry + * @param key + * The string name of the key to retrieve + * @param defaultValue + * The value to return if unsuccessful + * @return The value of the registry entry if it exists; {@code defaultValue} + * otherwise + */ + public static boolean getIORegistryBooleanProperty(IORegistryEntry entry, String key, boolean defaultValue) { + boolean value = defaultValue; + CFStringRef keyAsCFString = CFStringRef.createCFString(key); + CFTypeRef valueAsCFType = IO.IORegistryEntryCreateCFProperty(entry, keyAsCFString, CF.CFAllocatorGetDefault(), + 0); + if (valueAsCFType != null) { + CFBooleanRef valueAsCFBoolean = new CFBooleanRef(valueAsCFType.getPointer()); + value = valueAsCFBoolean.booleanValue(); + } + keyAsCFString.release(); + if (valueAsCFType != null) { + valueAsCFType.release(); + } + return value; + } + + /** + * Convenience method to get a byte array value from an IO Registry. + * + * @param entry + * A handle to the registry entry + * @param key + * The string name of the key to retrieve + * @return The value of the registry entry if it exists; {@code null} otherwise + */ + public static byte[] getIORegistryByteArrayProperty(IORegistryEntry entry, String key) { + byte[] value = null; + CFStringRef keyAsCFString = CFStringRef.createCFString(key); + CFTypeRef valueAsCFType = IO.IORegistryEntryCreateCFProperty(entry, keyAsCFString, CF.CFAllocatorGetDefault(), + 0); + if (valueAsCFType != null) { + CFDataRef valueAsCFData = new CFDataRef(valueAsCFType.getPointer()); + int length = CF.CFDataGetLength(valueAsCFData).intValue(); + Pointer p = CF.CFDataGetBytePtr(valueAsCFData); + value = p.getByteArray(0, length); + } + keyAsCFString.release(); + if (valueAsCFType != null) { + valueAsCFType.release(); + } + return value; + } +} diff --git a/contrib/platform/test/com/sun/jna/platform/mac/DiskArbitrationTest.java b/contrib/platform/test/com/sun/jna/platform/mac/DiskArbitrationTest.java index 05028f3e4c..6906ca19ff 100644 --- a/contrib/platform/test/com/sun/jna/platform/mac/DiskArbitrationTest.java +++ b/contrib/platform/test/com/sun/jna/platform/mac/DiskArbitrationTest.java @@ -25,6 +25,7 @@ package com.sun.jna.platform.mac; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -87,9 +88,18 @@ public void testDiskCreate() { assertNotNull(cfWhole); CFBooleanRef cfWholeBool = new CFBooleanRef(cfWhole.getPointer()); if (cfWholeBool.booleanValue()) { + // check that util boolean matches + assertTrue(IOKitUtil.getIORegistryBooleanProperty(media, "Whole", false)); + // check long and int values for major + long majorLong = IOKitUtil.getIORegistryLongProperty(media, "BSD Major", Long.MAX_VALUE); + int majorInt = IOKitUtil.getIORegistryIntProperty(media, "BSD Major", Integer.MAX_VALUE); + assertEquals(majorLong, majorInt); + DADiskRef disk = DA.DADiskCreateFromIOMedia(CF.CFAllocatorGetDefault(), session, media); bsdNames.add(DA.DADiskGetBSDName(disk)); disk.release(); + } else { + assertFalse(IOKitUtil.getIORegistryBooleanProperty(media, "Whole", true)); } cfWhole.release(); assertEquals(0, media.release()); diff --git a/contrib/platform/test/com/sun/jna/platform/mac/IOKitTest.java b/contrib/platform/test/com/sun/jna/platform/mac/IOKitTest.java index 8e6d2636ba..93c1e83f8d 100644 --- a/contrib/platform/test/com/sun/jna/platform/mac/IOKitTest.java +++ b/contrib/platform/test/com/sun/jna/platform/mac/IOKitTest.java @@ -63,9 +63,7 @@ public class IOKitTest { @Test public void testMatching() { - PointerByReference masterPortPtr = new PointerByReference(); - assertEquals(0, IO.IOMasterPort(IOKit.MACH_PORT_NULL, masterPortPtr)); - MachPort masterPort = new MachPort(masterPortPtr.getValue()); + MachPort masterPort = IOKitUtil.getMasterPort(); String match = "matching BSD Name"; CFMutableDictionaryRef dict = IO.IOBSDNameMatching(masterPort, 0, match); @@ -103,7 +101,10 @@ public void testMatching() { assertNotNull(cfSerialAsType); CFStringRef cfSerial = new CFStringRef(cfSerialAsType.getPointer()); String serialNumber = cfSerial.stringValue(); - cfSerialAsType.release(); + + // Test util method for the same thing + String serialNumberViaUtil = IOKitUtil.getIORegistryStringProperty(platformExpert, "IOPlatformSerialNumber"); + assertEquals(serialNumber, serialNumberViaUtil); assertEquals(12, serialNumber.length()); // Get all the keys @@ -120,8 +121,8 @@ public void testMatching() { assertEquals(0, platformExpert.release()); // Get a single key from a nested entry - IORegistryEntry root = IO.IORegistryGetRootEntry(masterPort); - assertNotEquals(0, root); + IORegistryEntry root = IOKitUtil.getRoot(); + assertNotNull(root); cfSerialAsType = IO.IORegistryEntrySearchCFProperty(root, "IOService", serialKey, CF.CFAllocatorGetDefault(), 0); // without recursive search should be null @@ -140,19 +141,12 @@ public void testMatching() { @Test public void testIteratorParentChild() { - PointerByReference masterPortPtr = new PointerByReference(); - assertEquals(0, IO.IOMasterPort(IOKit.MACH_PORT_NULL, masterPortPtr)); - MachPort masterPort = new MachPort(masterPortPtr.getValue()); + MachPort masterPort = IOKitUtil.getMasterPort(); Set uniqueEntryIdSet = new HashSet<>(); - // Create matching dictionary for USB Controller class - CFMutableDictionaryRef dict = IO.IOServiceMatching("IOUSBController"); // Iterate over USB Controllers. All devices are children of one of // these controllers in the "IOService" plane - PointerByReference iterPtr = new PointerByReference(); - assertEquals(0, IO.IOServiceGetMatchingServices(masterPort, dict, iterPtr)); - IOIterator iter = new IOIterator(iterPtr.getValue()); - // iter is a pointer to first device; iterate until null + IOIterator iter = IOKitUtil.getMatchingServices("IOUSBController"); IORegistryEntry controllerDevice = iter.next(); while (controllerDevice != null) { LongByReference id = new LongByReference(); @@ -221,17 +215,12 @@ public void testIteratorParentChild() { @Test public void testIOConnect() { - PointerByReference masterPortPtr = new PointerByReference(); - assertEquals(0, IO.IOMasterPort(IOKit.MACH_PORT_NULL, masterPortPtr)); - MachPort masterPort = new MachPort(masterPortPtr.getValue()); - - // Open a connection to SMC - CFMutableDictionaryRef dict = IO.IOServiceMatching("AppleSMC"); - // consumes dict references - IOService smcService = IO.IOServiceGetMatchingService(masterPort, dict); - assertNotEquals(0, smcService); - PointerByReference connPtr = new PointerByReference(); + MachPort masterPort = IOKitUtil.getMasterPort(); + IOService smcService = IOKitUtil.getMatchingService("AppleSMC"); + assertNotNull(smcService); + + PointerByReference connPtr = new PointerByReference(); MachPort taskSelf = SystemB.INSTANCE.mach_task_self_ptr(); assertEquals(0, IO.IOServiceOpen(smcService, taskSelf, 0, connPtr)); IOConnect conn = new IOConnect(connPtr.getValue());