From 0a2ef5ec3012475f33de38b7d3862a1acb32959c Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Thu, 15 Apr 2021 16:29:40 -0700 Subject: [PATCH] Added CM_Get_DevNode_Registry_Property to Cfgmgr32 and Cfgmgr32Util --- CHANGES.md | 1 + .../sun/jna/platform/win32/Advapi32Util.java | 13 +++ .../com/sun/jna/platform/win32/Cfgmgr32.java | 89 ++++++++++++-- .../sun/jna/platform/win32/Cfgmgr32Util.java | 102 +++++++++++++++- .../sun/jna/platform/win32/Cfgmgr32Test.java | 109 +++++++++++++++--- 5 files changed, 287 insertions(+), 27 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ee84477b42..3f68404940 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ Features * [#1336](https://github.com/java-native-access/jna/pull/1336): Add `HKEY_CURRENT_USER_LOCAL_SETTINGS` to `c.s.j.p.win32.WinReg` - [@Dani-Hub](https://github.com/Dani-Hub). * [#1337](https://github.com/java-native-access/jna/pull/1337): Add `REG_NOTIFY_THREAD_AGNOSTIC` to `c.s.j.p.win32.WinNet` and update `REG_LEGAL_CHANGE_FILTER` - [@Dani-Hub](https://github.com/Dani-Hub). * [#1338](https://github.com/java-native-access/jna/pull/1338): Add `RegNotifyChangeKeyValue` to `c.s.j.p.win32.Advapi32` - [@Dani-Hub](https://github.com/Dani-Hub). +* [#1340](https://github.com/java-native-access/jna/issues/1340): Added `CM_Get_DevNode_Registry_Property` to `c.s.j.p.win32.Cfgmgr32` and corresponding util in `c.s.j.p.win32.Cfgmgr32Util` - [@dbwiddis](https://github.com/dbwiddis). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java b/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java index df4d06f795..bdb18e62bd 100755 --- a/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java @@ -961,6 +961,19 @@ public static String[] registryGetStringArray(HKEY hKey, String value) { && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) { throw new Win32Exception(rc); } + return regMultiSzBufferToStringArray(data); + } + + /** + * Convert the null-delimited buffer of strings returned from registry values of + * type {@link WinNT.REG_MULTI_SZ} to an array of strings. + * + * @param data + * A buffer containing strings delimited by a null character, ending + * with two null characters. + * @return An array of strings corresponding to the strings in the buffer. + */ + static String[] regMultiSzBufferToStringArray(Memory data) { ArrayList result = new ArrayList(); int offset = 0; while (offset < data.size()) { diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32.java b/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32.java index 17cd4b5759..65a2cafbe7 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32.java @@ -37,14 +37,50 @@ public interface Cfgmgr32 extends Library { Cfgmgr32 INSTANCE = Native.load("Cfgmgr32", Cfgmgr32.class, W32APIOptions.DEFAULT_OPTIONS); - public final static int CR_SUCCESS = 0; - public final static int CR_BUFFER_SMALL = 0x0000001A; + int CR_SUCCESS = 0; + int CR_BUFFER_SMALL = 0x0000001A; + int CR_NO_SUCH_VALUE = 0x00000025; - public final static int CM_LOCATE_DEVNODE_NORMAL = 0; - public final static int CM_LOCATE_DEVNODE_PHANTOM = 1; - public final static int CM_LOCATE_DEVNODE_CANCELREMOVE = 2; - public final static int CM_LOCATE_DEVNODE_NOVALIDATION = 4; - public final static int CM_LOCATE_DEVNODE_BITS = 7; + int CM_LOCATE_DEVNODE_NORMAL = 0; + int CM_LOCATE_DEVNODE_PHANTOM = 1; + int CM_LOCATE_DEVNODE_CANCELREMOVE = 2; + int CM_LOCATE_DEVNODE_NOVALIDATION = 4; + int CM_LOCATE_DEVNODE_BITS = 7; + + int CM_DRP_DEVICEDESC = 0x00000001; // DeviceDesc REG_SZ property (RW) + int CM_DRP_HARDWAREID = 0x00000002; // HardwareID REG_MULTI_SZ property (RW) + int CM_DRP_COMPATIBLEIDS = 0x00000003; // CompatibleIDs REG_MULTI_SZ property (RW) + int CM_DRP_SERVICE = 0x00000005; // Service REG_SZ property (RW) + int CM_DRP_CLASS = 0x00000008; // Class REG_SZ property (RW) + int CM_DRP_CLASSGUID = 0x00000009; // ClassGUID REG_SZ property (RW) + int CM_DRP_DRIVER = 0x0000000A; // Driver REG_SZ property (RW) + int CM_DRP_CONFIGFLAGS = 0x0000000B; // ConfigFlags REG_DWORD property (RW) + int CM_DRP_MFG = 0x0000000C; // Mfg REG_SZ property (RW) + int CM_DRP_FRIENDLYNAME = 0x0000000D; // FriendlyName REG_SZ property (RW) + int CM_DRP_LOCATION_INFORMATION = 0x0000000E; // LocationInformation REG_SZ property (RW) + int CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000F; // PhysicalDeviceObjectName REG_SZ property (R) + int CM_DRP_CAPABILITIES = 0x00000010; // Capabilities REG_DWORD property (R) + int CM_DRP_UI_NUMBER = 0x00000011; // UiNumber REG_DWORD property (R) + int CM_DRP_UPPERFILTERS = 0x00000012; // UpperFilters REG_MULTI_SZ property (RW) + int CM_DRP_LOWERFILTERS = 0x00000013; // LowerFilters REG_MULTI_SZ property (RW) + int CM_DRP_BUSTYPEGUID = 0x00000014; // Bus Type Guid, GUID, (R) + int CM_DRP_LEGACYBUSTYPE = 0x00000015; // Legacy bus type, INTERFACE_TYPE, (R) + int CM_DRP_BUSNUMBER = 0x00000016; // Bus Number, DWORD, (R) + int CM_DRP_ENUMERATOR_NAME = 0x00000017; // Enumerator Name REG_SZ property (R) + int CM_DRP_SECURITY = 0x00000018; // Security - Device override (RW) + int CM_DRP_SECURITY_SDS = 0x00000019; // Security - Device override (RW) + int CM_DRP_DEVTYPE = 0x0000001A; // Device Type - Device override (RW) + int CM_DRP_EXCLUSIVE = 0x0000001B; // Exclusivity - Device override (RW) + int CM_DRP_CHARACTERISTICS = 0x0000001C; // Characteristics - Device Override (RW) + int CM_DRP_ADDRESS = 0x0000001D; // Device Address (R) + int CM_DRP_UI_NUMBER_DESC_FORMAT = 0x0000001E; // UINumberDescFormat REG_SZ property (RW) + int CM_DRP_DEVICE_POWER_DATA = 0x0000001F; // CM_POWER_DATA REG_BINARY property (R) + int CM_DRP_REMOVAL_POLICY = 0x00000020; // CM_DEVICE_REMOVAL_POLICY REG_DWORD (R) + int CM_DRP_REMOVAL_POLICY_HW_DEFAULT = 0x00000021; // CM_DRP_REMOVAL_POLICY_HW_DEFAULT REG_DWORD (R) + int CM_DRP_REMOVAL_POLICY_OVERRIDE = 0x00000022; // CM_DRP_REMOVAL_POLICY_OVERRIDE REG_DWORD (RW) + int CM_DRP_INSTALL_STATE = 0x00000023; // CM_DRP_INSTALL_STATE REG_DWORD (R) + int CM_DRP_LOCATION_PATHS = 0x00000024; // CM_DRP_LOCATION_PATHS REG_MULTI_SZ (R) + int CM_DRP_BASE_CONTAINERID = 0x00000025; // Base ContainerID REG_SZ property (R) /** * The CM_Locate_DevNode function obtains a device instance handle to the @@ -189,4 +225,43 @@ public interface Cfgmgr32 extends Library { * CM_Get_Device_ID_Size */ int CM_Get_Device_ID_Size(IntByReference pulLen, int dnDevInst, int ulFlags); + + /** + * The CM_Get_DevNode_Registry_Property function retrieves a specified device + * property from the registry. + * + * @param dnDevInst + * A caller-supplied device instance handle that is bound to the + * local machine. + * @param ulProperty + * A {@code CM_DRP_}-prefixed constant value that identifies the + * device property to be obtained from the registry. These constants + * are defined in Cfgmgr32.h. + * @param pulRegDataType + * Optional, can be {@code null}. A pointer to a location that + * receives the registry data type, specified as a + * {@code REG_}-prefixed constant defined in Winnt.h. + * @param buffer + * Optional, can be {@code null}. A pointer to a caller-supplied + * buffer that receives the requested device property. If this value + * is {@code null}, the function supplies only the length of the + * requested data in the address pointed to by {@code pulLength}. + * @param pulLength + * A pointer to a {@code ULONG} variable into which the function + * stores the length, in bytes, of the requested device property. + *

+ * If the Buffer parameter is set to {@code null}, the ULONG variable + * must be set to zero. + *

+ * If the Buffer parameter is not set to {@code null}, the + * {@code ULONG} variable must be set to the length, in bytes, of the + * caller-supplied buffer. + * @param ulFlags + * Not used, must be zero. + * @return If the operation succeeds, the function returns {@code CR_SUCCESS}. + * Otherwise, it returns one of the {@code CR_}-prefixed error codes + * that are defined in Cfgmgr32.h. + */ + int CM_Get_DevNode_Registry_Property(int dnDevInst, int ulProperty, IntByReference pulRegDataType, Pointer buffer, + IntByReference pulLength, int ulFlags); } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32Util.java b/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32Util.java index 664ecbd053..686da23797 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32Util.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32Util.java @@ -1,4 +1,4 @@ -/* Copyright (c) 2018 Daniel Widdis, All Rights Reserved +/* Copyright (c) 2018, 2021 Daniel Widdis, All Rights Reserved * * The contents of this file is dual-licensed under 2 * alternative Open Source/Free licenses: LGPL 2.1 or later and @@ -26,11 +26,10 @@ import com.sun.jna.Memory; import com.sun.jna.Native; import com.sun.jna.ptr.IntByReference; +import com.sun.jna.win32.W32APITypeMapper; /** * Cfgmgr32 utility API. - * - * @author widdis[at]gmail[dot]com */ public abstract class Cfgmgr32Util { @SuppressWarnings("serial") @@ -47,13 +46,13 @@ public int getErrorCode() { } /** - * Utility method to call Cfgmgr32's CM_Get_Device_ID that allocates the + * Utility method to call Cfgmgr32's CM_Get_Device_ID_Size, allocates the * required memory for the Buffer parameter based on the type mapping used, * calls to CM_Get_Device_ID, and returns the received string. * * @param devInst - * Caller-supplied device instance handle that is bound to the - * local machine. + * Caller-supplied device instance handle that is bound to the local + * machine. * @return The device instance ID string. * @throws Cfgmgr32Exception */ @@ -96,4 +95,95 @@ public static String CM_Get_Device_ID(int devInst) throws Cfgmgr32Exception { return buffer.getWideString(0); } } + + /** + * Utility method to call Cfgmgr32's CM_Get_DevNode_Registry_Property that + * allocates the required memory for the Buffer parameter, and returns values of + * the appropriate type. + * + * @param devInst + * Caller-supplied device instance handle that is bound to the local + * machine. + * @param ulProperty + * A {@code CM_DRP_}-prefixed constant value that identifies the + * device property to be obtained from the registry. These constants + * are defined in Cfgmgr32.h. + * @return An {@link Object} containing the specified registry property for the + * device. + *

+ * If the property is of type {@link WinNT.REG_SZ}, a + * {@link java.lang.String} is returned. + *

+ * If the property is of type {@link WinNT.REG_MULTI_SZ}, an array of + * {@link java.lang.String} is returned. + *

+ * If the property is of type {@link WinNT.REG_DWORD}, an + * {@link java.lang.Integer} is returned. + *

+ * If the property is of type {@link WinNT.REG_BINARY}, an array of + * {@link java.lang.Byte} is returned. + *

+ * If no value exists for this property (error + * {@link Cfgmgr32.CR_NO_SUCH_VALUE}), returns {@code null}. + * @throws Cfgmgr32Exception + * on any errors other than {@link Cfgmgr32.CR_NO_SUCH_VALUE} + */ + public static Object CM_Get_DevNode_Registry_Property(int devInst, int ulProperty) throws Cfgmgr32Exception { + + // Get byte count and type + IntByReference size = new IntByReference(); + IntByReference type = new IntByReference(); + int ret = Cfgmgr32.INSTANCE.CM_Get_DevNode_Registry_Property(devInst, ulProperty, type, null, size, 0); + // If this property does not exist return null + if (ret == Cfgmgr32.CR_NO_SUCH_VALUE) { + return null; + } + // If successful in retrieving type and size, should fail with CR_BUFFER_SMALL, + // otherwise throw an exception + if (ret != Cfgmgr32.CR_BUFFER_SMALL) { + throw new Cfgmgr32Exception(ret); + } + + // It is possible to have a valid value with registry data type, but 0 size. + // Leave the memory buffer null in that case + Memory buffer = null; + if (size.getValue() > 0) { + buffer = new Memory(size.getValue()); + ret = Cfgmgr32.INSTANCE.CM_Get_DevNode_Registry_Property(devInst, ulProperty, type, buffer, size, 0); + if (ret != Cfgmgr32.CR_SUCCESS) { + throw new Cfgmgr32Exception(ret); + } + } + + // Get the appropriate type of data from the buffer + switch (type.getValue()) { + case WinNT.REG_SZ: + // Convert buffer to Java String + if (buffer == null) { + return ""; + } + return W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE ? buffer.getWideString(0) : buffer.getString(0); + case WinNT.REG_MULTI_SZ: + // Convert buffer to String Array + if (buffer == null) { + return new String[0]; + } + return Advapi32Util.regMultiSzBufferToStringArray(buffer); + case WinNT.REG_DWORD: + // Convert buffer to int + if (buffer == null) { + return 0; + } + return buffer.getInt(0); + case WinNT.REG_NONE: + return null; + default: + // Intended for WinNT.REG_BINARY but safe default for any data + if (buffer == null) { + return new byte[0]; + } + // Convert buffer to array of bytes + return buffer.getByteArray(0, (int) buffer.size()); + } + } } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Cfgmgr32Test.java b/contrib/platform/test/com/sun/jna/platform/win32/Cfgmgr32Test.java index ec77e1da66..64335c6018 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/Cfgmgr32Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/Cfgmgr32Test.java @@ -1,4 +1,4 @@ -/* Copyright (c) 2018 Daniel Widdis, All Rights Reserved +/* Copyright (c) 2018, 2021 Daniel Widdis, All Rights Reserved * * The contents of this file is dual-licensed under 2 * alternative Open Source/Free licenses: LGPL 2.1 or later and @@ -23,10 +23,19 @@ */ package com.sun.jna.platform.win32; +import static com.sun.jna.platform.win32.Cfgmgr32.CM_DRP_CONFIGFLAGS; +import static com.sun.jna.platform.win32.Cfgmgr32.CM_DRP_DEVICEDESC; +import static com.sun.jna.platform.win32.Cfgmgr32.CM_DRP_DEVICE_POWER_DATA; +import static com.sun.jna.platform.win32.Cfgmgr32.CM_DRP_HARDWAREID; +import static com.sun.jna.platform.win32.Cfgmgr32.CM_LOCATE_DEVNODE_NORMAL; +import static com.sun.jna.platform.win32.Cfgmgr32.CR_SUCCESS; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.UnsupportedEncodingException; +import java.util.ArrayDeque; +import java.util.Queue; import org.junit.Test; @@ -34,10 +43,10 @@ /** * Tests methods in Cfgmgr32 - * - * @author widdis[at]gmail[dot]com */ public class Cfgmgr32Test { + private static final Cfgmgr32 CFG = Cfgmgr32.INSTANCE; + /** * Tests CM_Locate_DevNode, CM_Get_Parent, CM_Get_Child, CM_Get_Sibling */ @@ -45,19 +54,18 @@ public class Cfgmgr32Test { public void testDevNode() { // Fetch the root node IntByReference outputNode = new IntByReference(); - assertEquals(Cfgmgr32.CR_SUCCESS, - Cfgmgr32.INSTANCE.CM_Locate_DevNode(outputNode, null, Cfgmgr32.CM_LOCATE_DEVNODE_NORMAL)); + assertEquals(CR_SUCCESS, CFG.CM_Locate_DevNode(outputNode, null, CM_LOCATE_DEVNODE_NORMAL)); // Get first child int rootNode = outputNode.getValue(); int inputNode = rootNode; - assertEquals(Cfgmgr32.CR_SUCCESS, Cfgmgr32.INSTANCE.CM_Get_Child(outputNode, inputNode, 0)); + assertEquals(CR_SUCCESS, CFG.CM_Get_Child(outputNode, inputNode, 0)); // Iterate this child and its siblings do { inputNode = outputNode.getValue(); // Get parent, confirm it matches root - assertEquals(Cfgmgr32.CR_SUCCESS, Cfgmgr32.INSTANCE.CM_Get_Parent(outputNode, inputNode, 0)); + assertEquals(CR_SUCCESS, CFG.CM_Get_Parent(outputNode, inputNode, 0)); assertEquals(rootNode, outputNode.getValue()); - } while (Cfgmgr32.CR_SUCCESS == Cfgmgr32.INSTANCE.CM_Get_Sibling(outputNode, inputNode, 0)); + } while (CR_SUCCESS == CFG.CM_Get_Sibling(outputNode, inputNode, 0)); } /** @@ -66,16 +74,15 @@ public void testDevNode() { * @throws UnsupportedEncodingException */ @Test - public void testDeviceID() { + public void testDeviceId() { // Fetch the root node IntByReference outputNode = new IntByReference(); - assertEquals(Cfgmgr32.CR_SUCCESS, - Cfgmgr32.INSTANCE.CM_Locate_DevNode(outputNode, null, Cfgmgr32.CM_LOCATE_DEVNODE_NORMAL)); + assertEquals(CR_SUCCESS, CFG.CM_Locate_DevNode(outputNode, null, CM_LOCATE_DEVNODE_NORMAL)); int rootNode = outputNode.getValue(); // Get Device ID character count IntByReference pulLen = new IntByReference(); - Cfgmgr32.INSTANCE.CM_Get_Device_ID_Size(pulLen, rootNode, 0); + CFG.CM_Get_Device_ID_Size(pulLen, rootNode, 0); assertTrue(pulLen.getValue() > 0); // Get Device ID from util @@ -83,8 +90,82 @@ public void testDeviceID() { assertEquals(pulLen.getValue(), deviceId.length()); // Look up node from device ID - assertEquals(Cfgmgr32.CR_SUCCESS, - Cfgmgr32.INSTANCE.CM_Locate_DevNode(outputNode, deviceId, Cfgmgr32.CM_LOCATE_DEVNODE_NORMAL)); + assertEquals(CR_SUCCESS, CFG.CM_Locate_DevNode(outputNode, deviceId, CM_LOCATE_DEVNODE_NORMAL)); assertEquals(rootNode, outputNode.getValue()); } + + /** + * Tests CM_Get_DevNode_Registry_Property util + */ + @Test + public void testDeviceProperties() { + // Test an invalid node + Object props = Cfgmgr32Util.CM_Get_DevNode_Registry_Property(-1, CM_DRP_DEVICEDESC); + assertNull(props); + + // Not all devices have all properties and will fail with CR_NO_SUCH_VALUE. + // So do BFS of device tree and run tests on all devices until we've tested each + boolean descTested = false; + boolean hwidTested = false; + boolean flagsTested = false; + boolean powerTested = false; + + // Fetch the root node + IntByReference outputNode = new IntByReference(); + assertEquals(CR_SUCCESS, CFG.CM_Locate_DevNode(outputNode, null, CM_LOCATE_DEVNODE_NORMAL)); + int node = outputNode.getValue(); + + // Navigate the device tree using BFS + Queue deviceQueue = new ArrayDeque(); + IntByReference child = new IntByReference(); + IntByReference sibling = new IntByReference(); + // Initialize queue with root node + deviceQueue.add(node); + while (!deviceQueue.isEmpty()) { + // Process the next device in the queue + node = deviceQueue.poll(); + + // Run tests + props = Cfgmgr32Util.CM_Get_DevNode_Registry_Property(node, CM_DRP_DEVICEDESC); + if (props != null) { + assertTrue(props instanceof String); + descTested = true; + } + props = Cfgmgr32Util.CM_Get_DevNode_Registry_Property(node, CM_DRP_HARDWAREID); + if (props != null) { + assertTrue(props instanceof String[]); + hwidTested = true; + } + props = Cfgmgr32Util.CM_Get_DevNode_Registry_Property(node, CM_DRP_CONFIGFLAGS); + if (props != null) { + assertTrue(props instanceof Integer); + flagsTested = true; + } + props = Cfgmgr32Util.CM_Get_DevNode_Registry_Property(node, CM_DRP_DEVICE_POWER_DATA); + if (props != null) { + assertTrue(props instanceof byte[]); + powerTested = true; + } + // Test an invalid type + assertNull(Cfgmgr32Util.CM_Get_DevNode_Registry_Property(node, 0)); + + // If we've done all tests we can exit the loop + if (descTested && hwidTested && flagsTested && powerTested) { + break; + } + + // If not done, add any children to the queue + if (CR_SUCCESS == CFG.CM_Get_Child(child, node, 0)) { + deviceQueue.add(child.getValue()); + while (CR_SUCCESS == CFG.CM_Get_Sibling(sibling, child.getValue(), 0)) { + deviceQueue.add(sibling.getValue()); + child.setValue(sibling.getValue()); + } + } + } + assertTrue(descTested); + assertTrue(hwidTested); + assertTrue(flagsTested); + assertTrue(powerTested); + } }