diff --git a/CHANGES.md b/CHANGES.md index a0d516b6b1..2ea2bbc6ab 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -48,6 +48,7 @@ Features * [#434](https://github.com/twall/jna/pull/434): Added GetEnvironmentStrings to 'com.sun.jna.platform.win32.Kernel32' - [@lgoldstein](https://github.com/lgoldstein). * Loosen OSGI OS name matching to accommodate Windows 8 family - Niels Bertram. * [#436] (https://github.com/twall/jna/pull/469): Added basic Pdh API implementation to 'com.sun.jna.platform.win32' - [@lgoldstein](https://github.com/lgoldstein). +* [#481] (https://github.com/twall/jna/pull/481): Added volume management functions to 'com.sun.jna.platform.win32' - [@lgoldstein](https://github.com/lgoldstein). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java index 3b72d6fbe3..1ffb32eb8b 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java @@ -324,8 +324,8 @@ boolean ReadFile(HANDLE hFile, Buffer lpBuffer, int nNumberOfBytesToRead, void SetLastError(int dwErrCode); /** - * The GetDriveType function determines whether a disk drive is a removable, - * fixed, CD-ROM, RAM disk, or network drive. + * Determines whether a disk drive is a removable, fixed, CD-ROM, RAM + * disk, or network drive. * * @param lpRootPathName * Pointer to a null-terminated string that specifies the root @@ -333,6 +333,7 @@ boolean ReadFile(HANDLE hFile, Buffer lpBuffer, int nNumberOfBytesToRead, * backslash is required. If this parameter is NULL, the function * uses the root of the current directory. * @return The return value specifies the type of drive. + * @see GetDriveType */ int GetDriveType(String lpRootPathName); @@ -2601,4 +2602,268 @@ boolean SystemTimeToTzSpecificLocalTime(TIME_ZONE_INFORMATION lpTimeZone, * documentation */ SIZE_T VirtualQueryEx(HANDLE hProcess, Pointer lpAddress, MEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength); + + /** + * Defines, redefines, or deletes MS-DOS device names. + * @param dwFlags The controllable aspects of the function - see the + * various {@code DDD_XXX} constants + * @param lpDeviceName The MS-DOS device name string specifying the device + * the function is defining, redefining, or deleting. The device name string + * must not have a colon as the last character, unless a drive letter is + * being defined, redefined, or deleted. For example, drive {@code C} would + * be the string "C:". In no case is a trailing backslash + * ("\") allowed. + * @param lpTargetPath The path string that will implement this device. + * The string is an MS-DOS path string unless the {@code DDD_RAW_TARGET_PATH} + * flag is specified, in which case this string is a path string. + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information + * @see DefineDosDevice + */ + boolean DefineDosDevice(int dwFlags, String lpDeviceName, String lpTargetPath); + + /** + * Retrieves information about MS-DOS device names + * @param lpDeviceName An MS-DOS device name string specifying the target + * of the query. The device name cannot have a trailing backslash; for + * example, use "C:", not "C:\". This parameter can be + * NULL. In that case, the function will store a list of all existing MS-DOS + * device names into the buffer. + * @param lpTargetPath A buffer that will receive the result of the query. + * The function fills this buffer with one or more null-terminated strings. + * The final null-terminated string is followed by an additional NULL. If + * device name is non-NULL, the function retrieves information about the + * particular MS-DOS device. The first null-terminated string stored into + * the buffer is the current mapping for the device. The other null-terminated + * strings represent undeleted prior mappings for the device. Each + * null-terminated string stored into the buffer is the name of an existing + * MS-DOS device, for example, {@code \Device\HarddiskVolume1} or {@code \Device\Floppy0}. + * @param ucchMax The maximum number of characters that can be stored into the buffer + * @return If the function succeeds, the return value is the number of characters stored + * into the buffer, otherwise zero. Use {@link #GetLastError()} to get extended + * error information. If the buffer is too small, the function fails and the last error + * code is {@code ERROR_INSUFFICIENT_BUFFER}. + * @see QueryDosDevice + */ + int QueryDosDevice(String lpDeviceName, char[] lpTargetPath, int ucchMax); + + /** + * Retrieves the name of a mounted folder on the specified volume - used + * to begin scanning the mounted folders on a volume + * @param lpszRootPathName A volume GUID path for the volume to scan for + * mounted folders. A trailing backslash is required. + * @param lpszVolumeMountPoint A buffer that receives the name of the first + * mounted folder that is found. + * @param cchBufferLength The length of the buffer that receives the path + * to the mounted folder + * @return If succeeds, a search handle used in a subsequent call to the + * FindNextVolumeMountPoint and FindVolumeMountPointClose + * functions. Otherwise, the return value is the {@link #INVALID_HANDLE_VALUE}. + * To get extended error information, call {@link #GetLastError()}. + * @see FindFirstVolumeMountPoint + */ + HANDLE FindFirstVolumeMountPoint(String lpszRootPathName, char[] lpszVolumeMountPoint, int cchBufferLength); + + /** + * Continues a mounted folder search started by a call to the + * {@link #FindFirstVolumeMountPoint(String, char[], int)} function - finds one + * (next) mounted folder per call. + * @param hFindVolumeMountPoint A mounted folder search handle returned by + * a previous call to the {@link #FindFirstVolumeMountPoint(String, char[], int)} + * function. + * @param lpszVolumeMountPoint A buffer that receives the name of the (next) + * mounted folder that is found. + * @param cchBufferLength The length of the buffer that receives the path + * to the mounted folder + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information. If no more mount points found then the reported + * error is {@code ERROR_NO_MORE_FILES}. In this case, simply call + * {@link #FindVolumeMountPointClose(com.sun.jna.platform.win32.WinNT.HANDLE)} + * @see FindNextVolumeMountPoint + */ + boolean FindNextVolumeMountPoint(HANDLE hFindVolumeMountPoint, char[] lpszVolumeMountPoint, int cchBufferLength); + + /** + * Closes the specified mounted folder search handle. + * @param hFindVolumeMountPoint A mounted folder search handle returned by + * a previous call to the {@link #FindFirstVolumeMountPoint(String, char[], int)} + * function. + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information + * @see FindVolumeMountPointClose + */ + boolean FindVolumeMountPointClose(HANDLE hFindVolumeMountPoint); + + /** + * Retrieves a volume GUID path for the volume that is associated with the + * specified volume mount point (drive letter, volume GUID path, or mounted + * folder). + * @param lpszVolumeMountPoint A string that contains the path of a mounted + * folder (e.g., "Y:\MountX\") or a drive letter (for example, + * "X:\"). The string must end with a trailing backslash. + * @param lpszVolumeName A buffer that receives the volume GUID path - if + * there is more than one volume GUID path for the volume, only the first + * one in the mount manager's cache is returned. + * @param cchBufferLength The length of the output buffer - a reasonable size + * for the buffer to accommodate the largest possible volume GUID path is + * at 50 characters + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information + * @see GetVolumeNameForVolumeMountPoint + */ + boolean GetVolumeNameForVolumeMountPoint(String lpszVolumeMountPoint, char[] lpszVolumeName, int cchBufferLength); + + /** + * Sets the label of a file system volume. + * @param lpRootPathName The volume's drive letter (for example, {@code X:\}) + * or the path of a mounted folder that is associated with the volume (for + * example, {@code Y:\MountX\}). The string must end with a trailing backslash. + * If this parameter is NULL, the root of the current directory is used. + * @param lpVolumeName The new label for the volume. If this parameter is NULL, + * the function deletes any existing label from the specified volume and does + * not assign a new label. + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information + * @see SetVolumeLabel + */ + boolean SetVolumeLabel(String lpRootPathName, String lpVolumeName); + + /** + * Associates a volume with a drive letter or a directory on another volume. + * @param lpszVolumeMountPoint The user-mode path to be associated with the + * volume. This may be a drive letter (for example, "X:\") or a + * directory on another volume (for example, "Y:\MountX\"). The + * string must end with a trailing backslash. + * @param lpszVolumeName A volume GUID path for the volume. + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information + * @see SetVolumeMountPoint + */ + boolean SetVolumeMountPoint(String lpszVolumeMountPoint, String lpszVolumeName); + + /** + * Deletes a drive letter or mounted folder + * @param lpszVolumeMountPoint The drive letter or mounted folder to be deleted. + * A trailing backslash is required, for example, "X:\" or "Y:\MountX\". + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information + * @see DeleteVolumeMountPoint + */ + boolean DeleteVolumeMountPoint(String lpszVolumeMountPoint); + + /** + * @param lpRootPathName A string that contains the root directory of the + * volume to be described. If this parameter is {@code null}, the root of + * the current directory is used. A trailing backslash is required. For example, + * you specify "\\MyServer\MyShare\", or "C:\". + * @param lpVolumeNameBuffer If not {@code null} then receives the name of + * the specified volume. The buffer size is specified by the nVolumeNameSize + * parameter. + * @param nVolumeNameSize The length of the volume name buffer - max. size is + * {@link WinDef#MAX_PATH} + 1 - ignored if no volume name buffer provided + * @param lpVolumeSerialNumber Receives the volume serial number - can be + * {@code null} if the serial number is not required + * @param lpMaximumComponentLength Receives the maximum length of a file name + * component that the underlying file system supports - can be {@code null} + * if this data is not required + * @param lpFileSystemFlags Receives flags associated with the file system + * - can be {@code null} if this data is not required + * @param lpFileSystemNameBuffer If not {@code null} then receives the name + * of the file system. The buffer size is specified by the nFileSystemNameSize + * parameter. + * @param nFileSystemNameSize The length of the file system name buffer - + * max. size is {@link WinDef#MAX_PATH} + 1 - ignored if no file system name + * buffer provided + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information + * @see GetVolumeInformation + */ + boolean GetVolumeInformation(String lpRootPathName, + char[] lpVolumeNameBuffer, int nVolumeNameSize, + IntByReference lpVolumeSerialNumber, + IntByReference lpMaximumComponentLength, + IntByReference lpFileSystemFlags, + char[] lpFileSystemNameBuffer, int nFileSystemNameSize); + + /** + * Retrieves the volume mount point where the specified path is mounted. + * @param lpszFileName The input path string. Both absolute and relative + * file and directory names, for example "..", are acceptable in + * this path. If you specify a relative directory or file name without a + * volume qualifier, returns the drive letter of the boot volume. If this + * parameter is an empty string, the function fails but the last error is + * set to {@code ERROR_SUCCESS}. + * @param lpszVolumePathName Buffer receives the volume mount point for the + * input path. + * @param cchBufferLength The length of the output buffer + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information + * @see GetVolumePathName + */ + boolean GetVolumePathName(String lpszFileName, char[] lpszVolumePathName, int cchBufferLength); + + /** + * Retrieves a list of drive letters and mounted folder paths for the specified volume + * @param lpszVolumeName A volume GUID path for the volume + * @param lpszVolumePathNames A buffer that receives the list of drive + * letters and mounted folder paths. The list is an array of null-terminated + * strings terminated by an additional NULL character. If the buffer is + * not large enough to hold the complete list, the buffer holds as much of + * the list as possible. + * @param cchBufferLength The available length of the buffer - including all + * NULL characters. + * @param lpcchReturnLength If the call is successful, this parameter is the + * number of character copied to the buffer. Otherwise, this parameter is the + * size of the buffer required to hold the complete list + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information. If the buffer is not large enough to hold + * the complete list, the error code is {@code ERROR_MORE_DATA} and the + * lpcchReturnLength parameter receives the required buffer size. + * @see GetVolumePathNamesForVolumeName + */ + boolean GetVolumePathNamesForVolumeName(String lpszVolumeName, + char[] lpszVolumePathNames, int cchBufferLength, + IntByReference lpcchReturnLength); + + /** + * Retrieves the name of a volume on a computer - used to begin scanning the + * volumes of a computer + * @param lpszVolumeName A buffer that receives a null-terminated string that + * specifies a volume GUID path for the first volume that is found + * @param cchBufferLength The length of the buffer to receive the volume GUID path + * @return If the function succeeds, the return value is a search handle + * used in a subsequent call to the {@link #FindNextVolume(com.sun.jna.platform.win32.WinNT.HANDLE, char[], int) + * and {@link #FindVolumeClose(com.sun.jna.platform.win32.WinNT.HANDLE)} functions. + * Otherwise, the return value is the {@link #INVALID_HANDLE_VALUE}. To get + * extended error information, call {@link #GetLastError()}. + * @see FindFirstVolume + * @see Kernel32Util#extractVolumeGUID(String) + */ + HANDLE FindFirstVolume(char[] lpszVolumeName, int cchBufferLength); + + /** + * Continues a volume search started by a call to the {@link #FindFirstVolume(char[], int)} + * function - finds one volume per call. + * @param hFindVolume The volume search handle returned by a previous call to the + * {@link #FindFirstVolume(char[], int)}. + * @param lpszVolumeName A buffer that receives a null-terminated string that + * specifies a volume GUID path for the (next) path that is found + * @param cchBufferLength The length of the buffer to receive the volume GUID path + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information. If no more volumes found then the reported + * error is {@code ERROR_NO_MORE_FILES}. In this case, simply call {@link #FindVolumeClose(com.sun.jna.platform.win32.WinNT.HANDLE)} + * @see FindNextVolume + * @see Kernel32Util#extractVolumeGUID(String) + */ + boolean FindNextVolume(HANDLE hFindVolume, char[] lpszVolumeName, int cchBufferLength); + + /** + * Closes the specified volume search handle. + * @param hFindVolume The volume search handle returned by a previous call to the + * {@link #FindFirstVolume(char[], int)}. + * @return {@code true} if succeeds. If fails then call {@link #GetLastError()} + * to get extended error information + * @see FindVolumeClose + */ + boolean FindVolumeClose(HANDLE hFindVolume); } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java index 576a61f309..90ab009d4d 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java @@ -15,7 +15,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.nio.ByteOrder; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -136,33 +135,22 @@ public static void deleteFile(String filename) { /** * Returns valid drives in the system. * - * @return An array of valid drives. + * @return A {@link List} of valid drives. */ - public static String[] getLogicalDriveStrings() { - DWORD dwSize = Kernel32.INSTANCE.GetLogicalDriveStrings(new DWORD(0), - null); + public static List getLogicalDriveStrings() { + DWORD dwSize = Kernel32.INSTANCE.GetLogicalDriveStrings(new DWORD(0), null); if (dwSize.intValue() <= 0) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } char buf[] = new char[dwSize.intValue()]; dwSize = Kernel32.INSTANCE.GetLogicalDriveStrings(dwSize, buf); - if (dwSize.intValue() <= 0) { + int bufSize = dwSize.intValue(); + if (bufSize <= 0) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } - List drives = new ArrayList(); - String drive = ""; - // the buffer is double-null-terminated - for (int i = 0; i < buf.length - 1; i++) { - if (buf[i] == 0) { - drives.add(drive); - drive = ""; - } else { - drive += buf[i]; - } - } - return drives.toArray(new String[0]); + return Native.toStringList(buf, 0, bufSize); } /** @@ -609,4 +597,76 @@ public static final void writePrivateProfileSection(final String appName, final throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } } + + /** + * Invokes the {@link Kernel32#QueryDosDevice(String, char[], int)} method + * and parses the result + * @param lpszDeviceName The device name + * @param maxTargetSize The work buffer size to use for the query + * @return The parsed result + */ + public static final List queryDosDevice(String lpszDeviceName, int maxTargetSize) { + char[] lpTargetPath = new char[maxTargetSize]; + int dwSize = Kernel32.INSTANCE.QueryDosDevice(lpszDeviceName, lpTargetPath, lpTargetPath.length); + if (dwSize == 0) { + throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); + } + + return Native.toStringList(lpTargetPath, 0, dwSize); + } + + /** + * Invokes and parses the result of {@link Kernel32#GetVolumePathNamesForVolumeName(String, char[], int, IntByReference)} + * @param lpszVolumeName The volume name + * @return The parsed result + * @throws Win32Exception If failed to retrieve the required information + */ + public static final List getVolumePathNamesForVolumeName(String lpszVolumeName) { + char[] lpszVolumePathNames = new char[WinDef.MAX_PATH + 1]; + IntByReference lpcchReturnLength = new IntByReference(); + + if (!Kernel32.INSTANCE.GetVolumePathNamesForVolumeName(lpszVolumeName, lpszVolumePathNames, lpszVolumePathNames.length, lpcchReturnLength)) { + int hr = Kernel32.INSTANCE.GetLastError(); + if (hr != WinError.ERROR_MORE_DATA) { + throw new Win32Exception(hr); + } + + int required = lpcchReturnLength.getValue(); + lpszVolumePathNames = new char[required]; + // this time we MUST succeed + if (!Kernel32.INSTANCE.GetVolumePathNamesForVolumeName(lpszVolumeName, lpszVolumePathNames, lpszVolumePathNames.length, lpcchReturnLength)) { + throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); + } + } + + int bufSize = lpcchReturnLength.getValue(); + return Native.toStringList(lpszVolumePathNames, 0, bufSize); + } + + // prefix and suffix of a volume GUID path + public static final String VOLUME_GUID_PATH_PREFIX = "\\\\?\\Volume{"; + public static final String VOLUME_GUID_PATH_SUFFIX = "}\\"; + + /** + * Parses and returns the pure GUID value of a volume name obtained + * from {@link Kernel32#FindFirstVolume(char[], int)} or + * {@link Kernel32#FindNextVolume(HANDLE, char[], int)} calls + * + * @param volumeName + * The volume name as returned by on of the above mentioned calls + * @return The pure GUID value after stripping the "\\?\" prefix and + * removing the trailing backslash. + * @throws IllegalArgumentException if bad format encountered + * @see Naming a Volume + */ + public static final String extractVolumeGUID(String volumeGUIDPath) { + if ((volumeGUIDPath == null) + || (volumeGUIDPath.length() <= (VOLUME_GUID_PATH_PREFIX.length() + VOLUME_GUID_PATH_SUFFIX.length())) + || (!volumeGUIDPath.startsWith(VOLUME_GUID_PATH_PREFIX)) + || (!volumeGUIDPath.endsWith(VOLUME_GUID_PATH_SUFFIX))) { + throw new IllegalArgumentException("Bad volume GUID path format: " + volumeGUIDPath); + } + + return volumeGUIDPath.substring(VOLUME_GUID_PATH_PREFIX.length(), volumeGUIDPath.length() - VOLUME_GUID_PATH_SUFFIX.length()); + } } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java b/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java index e383d3d6c4..382750af2f 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java @@ -722,6 +722,19 @@ public abstract class SID_NAME_USE { int FILE_READ_ONLY_VOLUME = 0x00080000; int FILE_SEQUENTIAL_WRITE_ONCE = 0x00100000; int FILE_SUPPORTS_TRANSACTIONS = 0x00200000; + // NOTE: These values are not supported until Windows Server 2008 R2 and Windows 7 + int FILE_SUPPORTS_HARD_LINKS = 0x00400000; + int FILE_SUPPORTS_EXTENDED_ATTRIBUTES = 0x00800000; + int FILE_SUPPORTS_OPEN_BY_FILE_ID = 0x01000000; + int FILE_SUPPORTS_USN_JOURNAL = 0x02000000; + + + // The controllable aspects of the DefineDosDevice function. + // see https://msdn.microsoft.com/en-us/library/windows/desktop/aa363904(v=vs.85).aspx + int DDD_RAW_TARGET_PATH = 0x00000001; + int DDD_REMOVE_DEFINITION = 0x00000002; + int DDD_EXACT_MATCH_ON_REMOVE = 0x00000004; + int DDD_NO_BROADCAST_SYSTEM = 0x00000008; /** * The FILE_NOTIFY_INFORMATION structure describes the changes found by the diff --git a/contrib/platform/test/com/sun/jna/platform/win32/AbstractWin32TestSupport.java b/contrib/platform/test/com/sun/jna/platform/win32/AbstractWin32TestSupport.java index 9f7143c23d..553f2a3c00 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/AbstractWin32TestSupport.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/AbstractWin32TestSupport.java @@ -13,6 +13,7 @@ package com.sun.jna.platform.win32; import com.sun.jna.platform.AbstractPlatformTestSupport; +import com.sun.jna.platform.win32.WinNT.HANDLE; /** * @author lgoldstein @@ -35,11 +36,11 @@ public static final void assertCallSucceeded(String message, boolean result) { return; } - int hr=Kernel32.INSTANCE.GetLastError(); + int hr = Kernel32.INSTANCE.GetLastError(); if (hr == WinError.ERROR_SUCCESS) { fail(message + " failed with unknown reason code"); } else { - fail(message + " failed: hr=0x" + Integer.toHexString(hr)); + fail(message + " failed: hr=" + hr + " - 0x" + Integer.toHexString(hr)); } } @@ -58,4 +59,26 @@ public static final void assertErrorSuccess(String message, int statusCode, bool assertEquals(message, WinError.ERROR_SUCCESS, statusCode); } } + + /** + * Makes sure that the handle argument is not {@code null} or {@link WinBase#INVALID_HANDLE_VALUE}. + * If invalid handle detected, then it invokes {@link Kernel32#GetLastError()} + * in order to display the error code + * @param message Message to display if bad handle + * @param handle The {@link HANDLE} to test + * @return The same as the input handle if good handle - otherwise does + * not return and throws an assertion error + */ + public static final HANDLE assertValidHandle(String message, HANDLE handle) { + if ((handle == null) || WinBase.INVALID_HANDLE_VALUE.equals(handle)) { + int hr = Kernel32.INSTANCE.GetLastError(); + if (hr == WinError.ERROR_SUCCESS) { + fail(message + " failed with unknown reason code"); + } else { + fail(message + " failed: hr=" + hr + " - 0x" + Integer.toHexString(hr)); + } + } + + return handle; + } } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java index 61562de9cf..ed2173d650 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java @@ -399,13 +399,6 @@ public void testGlobalMemoryStatusEx() { assertEquals(0, lpBuffer.ullAvailExtendedVirtual.intValue()); } - public void testGetLogicalDriveStrings() { - DWORD dwSize = Kernel32.INSTANCE.GetLogicalDriveStrings(new DWORD(0), null); - assertTrue(dwSize.intValue() > 0); - char buf[] = new char[dwSize.intValue()]; - assertTrue(Kernel32.INSTANCE.GetLogicalDriveStrings(dwSize, buf).intValue() > 0); - } - public void testGetDiskFreeSpaceEx() { LARGE_INTEGER.ByReference lpFreeBytesAvailable = new LARGE_INTEGER.ByReference(); LARGE_INTEGER.ByReference lpTotalNumberOfBytes = new LARGE_INTEGER.ByReference(); diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java index 3bf889db81..523d183f2a 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java @@ -19,11 +19,12 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; - -import junit.framework.TestCase; +import java.util.Collection; import com.sun.jna.platform.win32.WinNT.LARGE_INTEGER; +import junit.framework.TestCase; + /** * @author dblock[at]dblock[dot]org * @author markus[at]headcrashing[dot]eu @@ -35,7 +36,7 @@ public static void main(String[] args) throws Exception { System.out.println("Temp path: " + Kernel32Util.getTempPath()); // logical drives System.out.println("Logical drives: "); - String[] logicalDrives = Kernel32Util.getLogicalDriveStrings(); + Collection logicalDrives = Kernel32Util.getLogicalDriveStrings(); for(String logicalDrive : logicalDrives) { // drive type System.out.println(" " + logicalDrive + " (" @@ -105,10 +106,10 @@ public void testGetTempPath() { } public void testGetLogicalDriveStrings() { - String[] logicalDrives = Kernel32Util.getLogicalDriveStrings(); - assertTrue(logicalDrives.length > 0); + Collection logicalDrives = Kernel32Util.getLogicalDriveStrings(); + assertTrue("No logical drives found", logicalDrives.size() > 0); for(String logicalDrive : logicalDrives) { - assertTrue(logicalDrive.length() > 0); + assertTrue("Empty logical drive name in list", logicalDrive.length() > 0); } } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32VolumeManagementFunctionsTest.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32VolumeManagementFunctionsTest.java new file mode 100644 index 0000000000..aba091b126 --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32VolumeManagementFunctionsTest.java @@ -0,0 +1,188 @@ +/* Copyright (c) 2007 Timothy Wall, All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package com.sun.jna.platform.win32; + +import java.io.File; +import java.util.Collection; +import java.util.List; + +import org.junit.Test; + +import com.sun.jna.Native; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.ptr.IntByReference; + +public class Kernel32VolumeManagementFunctionsTest extends AbstractWin32TestSupport { + public Kernel32VolumeManagementFunctionsTest() { + super(); + } + + @Test + public void testQueryDosDevice() { + Collection logicalDrives = Kernel32Util.getLogicalDriveStrings(); + for (String lpszDeviceName : logicalDrives) { + // the documentation states that the device name cannot have a trailing backslash + if (lpszDeviceName.charAt(lpszDeviceName.length() - 1) == File.separatorChar) { + lpszDeviceName = lpszDeviceName.substring(0, lpszDeviceName.length() - 1); + } + + Collection devices = Kernel32Util.queryDosDevice(lpszDeviceName, WinBase.MAX_PATH); + assertTrue("No devices for " + lpszDeviceName, devices.size() > 0); + for (String name : devices) { + assertTrue("Empty device name for " + lpszDeviceName, name.length() > 0); +// System.out.append(getCurrentTestName()).append('[').append(lpszDeviceName).append(']').append(" - ").println(name); + } + } + } + + @Test + public void testGetVolumePathName() { + char[] lpszVolumePathName = new char[WinDef.MAX_PATH + 1]; + for (String propName : new String[] { "java.home", "java.io.tmpdir", "user.dir", "user.home" }) { + String lpszFileName = System.getProperty(propName); + assertCallSucceeded("GetVolumePathName(" + lpszFileName + ")", + Kernel32.INSTANCE.GetVolumePathName(lpszFileName, lpszVolumePathName, lpszVolumePathName.length)); + String path = Native.toString(lpszVolumePathName); +// System.out.append(getCurrentTestName()).append('[').append(lpszFileName).append(']').append(" - ").println(path); + assertTrue("No volume path for " + lpszFileName, path.length() > 0); + } + } + + @Test + public void testGetVolumeNameForVolumeMountPoint() { + Collection logicalDrives = Kernel32Util.getLogicalDriveStrings(); + char[] lpVolumeNameBuffer = new char[WinDef.MAX_PATH + 1]; + for (String lpszVolumeMountPoint : logicalDrives) { + // according to documentation path MUST end in backslash + if (lpszVolumeMountPoint.charAt(lpszVolumeMountPoint.length() - 1) != File.separatorChar) { + lpszVolumeMountPoint += File.separator; + } + + int driveType = Kernel32.INSTANCE.GetDriveType(lpszVolumeMountPoint); + // network mapped drives fail GetVolumeNameForVolumeMountPoint call + if (driveType != WinBase.DRIVE_FIXED) { +// System.out.append('\t').append('[').append(lpszVolumeMountPoint).append(']').println(" - skipped: non-fixed drive"); + continue; + } + + if (Kernel32.INSTANCE.GetVolumeNameForVolumeMountPoint(lpszVolumeMountPoint, lpVolumeNameBuffer, lpVolumeNameBuffer.length)) { + String volumeGUID = Native.toString(lpVolumeNameBuffer); +// System.out.append(getCurrentTestName()).append('[').append(lpszVolumeMountPoint).append(']').append(" - ").println(volumeGUID); + assertTrue("Empty GUID for " + lpszVolumeMountPoint, volumeGUID.length() > 0); + } else { + int hr = Kernel32.INSTANCE.GetLastError(); + if ((hr == WinError.ERROR_ACCESS_DENIED) // e.g., hidden volumes + || (hr == WinError.ERROR_NOT_READY)) { // e.g., DVD drive +// System.out.append('\t').append('[').append(lpszVolumeMountPoint).append(']').append(" - skipped: reason=").println(hr); + continue; + } + + fail("Cannot (error=" + hr + ") get volume information mount point " + lpszVolumeMountPoint); + } + } + } + + @Test + public void testGetVolumeInformation() { + List logicalDrives = Kernel32Util.getLogicalDriveStrings(); + char[] lpVolumeNameBuffer = new char[WinDef.MAX_PATH + 1]; + char[] lpFileSystemNameBuffer = new char[WinDef.MAX_PATH + 1]; + IntByReference lpVolumeSerialNumber = new IntByReference(); + IntByReference lpMaximumComponentLength = new IntByReference(); + IntByReference lpFileSystemFlags = new IntByReference(); + + for (int index=(-1); index < logicalDrives.size(); index++) { + String lpRootPathName = (index < 0) ? null /* curdir */ : logicalDrives.get(index); + // according to documentation path MUST end in backslash + if ((lpRootPathName != null) && (lpRootPathName.charAt(lpRootPathName.length() - 1) != File.separatorChar)) { + lpRootPathName += File.separator; + } + + if (!Kernel32.INSTANCE.GetVolumeInformation(lpRootPathName, + lpVolumeNameBuffer, lpVolumeNameBuffer.length, + lpVolumeSerialNumber, lpMaximumComponentLength, lpFileSystemFlags, + lpFileSystemNameBuffer, lpFileSystemNameBuffer.length)) { + int hr = Kernel32.INSTANCE.GetLastError(); + if ((hr == WinError.ERROR_ACCESS_DENIED) // e.g., network or hidden volumes + || (hr == WinError.ERROR_NOT_READY)) { // e.g., DVD drive +// System.out.append('\t').append('[').append(lpRootPathName).append(']').append(" - skipped: reason=").println(hr); + continue; + } + + fail("Cannot (error=" + hr + ") get volume information for " + lpRootPathName); + } + +// System.out.append(getCurrentTestName()).append('[').append(lpRootPathName).println(']'); +// System.out.append('\t').append("Volume name: ").println(Native.toString(lpVolumeNameBuffer)); +// System.out.append('\t').append("File system name: ").println(Native.toString(lpFileSystemNameBuffer)); +// System.out.append('\t').append("Serial number: ").println(lpVolumeSerialNumber.getValue()); +// System.out.append('\t').append("Max. component: ").println(lpMaximumComponentLength.getValue()); +// System.out.append('\t').append("File system flags: 0x").println(Integer.toHexString(lpFileSystemFlags.getValue())); + } + } + + @Test + public void testEnumVolumes() { + char[] lpszVolumeName = new char[WinDef.MAX_PATH + 1]; + HANDLE hFindVolume = assertValidHandle("FindFirstVolume", Kernel32.INSTANCE.FindFirstVolume(lpszVolumeName, lpszVolumeName.length)); + try { + do { + String volumeGUID = Native.toString(lpszVolumeName); + testEnumVolumeMountMoints(volumeGUID); + testGetVolumePathNamesForVolumeName(volumeGUID); + } while(Kernel32.INSTANCE.FindNextVolume(hFindVolume, lpszVolumeName, lpszVolumeName.length)); + + int hr = Kernel32.INSTANCE.GetLastError(); + assertEquals("Bad volumes enum termination reason", WinError.ERROR_NO_MORE_FILES, hr); + } finally { + assertCallSucceeded("FindVolumeClose", Kernel32.INSTANCE.FindVolumeClose(hFindVolume)); + } + } + + private void testGetVolumePathNamesForVolumeName(String lpszVolumeName) { + Collection paths = Kernel32Util.getVolumePathNamesForVolumeName(lpszVolumeName); + assertTrue("No paths for volume " + lpszVolumeName, paths.size() > 0); + for (String p : paths) { +// System.out.append('\t').append("testGetVolumePathNamesForVolumeName").append('[').append(lpszVolumeName).append(']').append(" - ").println(p); + assertTrue("Empty path for volume " + lpszVolumeName, p.length() > 0); + } + } + + private void testEnumVolumeMountMoints(String volumeGUID) { + char[] lpszVolumeMountPoint = new char[WinDef.MAX_PATH + 1]; + HANDLE hFindVolumeMountPoint = Kernel32.INSTANCE.FindFirstVolumeMountPoint(volumeGUID, lpszVolumeMountPoint, lpszVolumeMountPoint.length); + if (WinNT.INVALID_HANDLE_VALUE.equals(hFindVolumeMountPoint)) { + int hr = Kernel32.INSTANCE.GetLastError(); + if ((hr == WinError.ERROR_ACCESS_DENIED) // e.g., network or hidden volumes + || (hr == WinError.ERROR_NOT_READY)) { // e.g., DVD drive +// System.out.append('\t').append('[').append(volumeGUID).append(']').append(" - skipped: reason=").println(hr); + return; + } + + fail("Cannot (error=" + hr + ") open mount point search handle for " + volumeGUID); + } + + try { + do { + String name = Native.toString(lpszVolumeMountPoint); + assertTrue("Empty mount point for " + volumeGUID, name.length() > 0); +// System.out.append('\t').append("testEnumVolumeMountMoints").append('[').append(volumeGUID).append(']').append(" - ").println(name); + } while(Kernel32.INSTANCE.FindNextVolumeMountPoint(hFindVolumeMountPoint, lpszVolumeMountPoint, lpszVolumeMountPoint.length)); + + int hr = Kernel32.INSTANCE.GetLastError(); + assertEquals("Mount points enum termination reason for " + volumeGUID, WinError.ERROR_NO_MORE_FILES, hr); + } finally { + assertCallSucceeded("FindVolumeMountPointClose(" + volumeGUID + ")", Kernel32.INSTANCE.FindVolumeMountPointClose(hFindVolumeMountPoint)); + } + } +} diff --git a/src/com/sun/jna/Native.java b/src/com/sun/jna/Native.java index 445a60f60a..53d99430ea 100644 --- a/src/com/sun/jna/Native.java +++ b/src/com/sun/jna/Native.java @@ -101,6 +101,7 @@ public final class Native implements Version { private static Map libraries = new WeakHashMap(); private static final UncaughtExceptionHandler DEFAULT_HANDLER = new UncaughtExceptionHandler() { + @Override public void uncaughtException(Callback c, Throwable e) { System.err.println("JNA: Callback " + c + " threw the following exception:"); e.printStackTrace(); @@ -168,12 +169,13 @@ public static float parseVersion(String v) { /** Force a dispose when the Native class is GC'd. */ private static final Object finalizer = new Object() { + @Override protected void finalize() { dispose(); } }; - /** Properly dispose of JNA functionality. + /** Properly dispose of JNA functionality. Called when this class is finalized and also from JNI when JNA's native shared library is unloaded. */ @@ -244,6 +246,7 @@ private Native() { } * * @deprecated Last error is always preserved */ + @Deprecated public static void setPreserveLastError(boolean enable) { } /** Indicates whether the system last error result is preserved @@ -253,6 +256,7 @@ public static void setPreserveLastError(boolean enable) { } * * @deprecated Last error is always preserved */ + @Deprecated public static boolean getPreserveLastError() { return true; } /** Utility method to get the native window ID for a Java {@link Window} @@ -307,51 +311,126 @@ public static Pointer getDirectBufferPointer(Buffer b) { private static native long _getDirectBufferPointer(Buffer b); - /** Obtain a Java String from the given native byte array. If there is + /** + * Obtain a Java String from the given native byte array. If there is * no NUL terminator, the String will comprise the entire array. The * encoding is obtained from {@link #getDefaultStringEncoding()}. + * + * @param buf The buffer containing the encoded bytes + * @see #toString(byte[], String) */ public static String toString(byte[] buf) { return toString(buf, getDefaultStringEncoding()); } - /** Obtain a Java String from the given native byte array, using the given + /** + * Obtain a Java String from the given native byte array, using the given * encoding. If there is no NUL terminator, the String will comprise the - * entire array. If the encoding parameter is null, - * the platform default encoding will be used. + * entire array. + * + * @param buf The buffer containing the encoded bytes + * @param encoding The encoding name - if {@code null} then the platform + * default encoding will be used */ public static String toString(byte[] buf, String encoding) { - String s = null; + int len = buf.length; + // find out the effective length + for (int index = 0; index < len; index++) { + if (buf[index] == 0) { + len = index; + break; + } + } + + if (len == 0) { + return ""; + } + if (encoding != null) { try { - s = new String(buf, encoding); + return new String(buf, 0, len, encoding); } catch(UnsupportedEncodingException e) { System.err.println("JNA Warning: Encoding '" + encoding + "' is unsupported"); } } - if (s == null) { - System.err.println("JNA Warning: Decoding with fallback " + System.getProperty("file.encoding")); - s = new String(buf); + + System.err.println("JNA Warning: Decoding with fallback " + System.getProperty("file.encoding")); + return new String(buf, 0, len); + } + + /** + * Obtain a Java String from the given native wchar_t array. If there is + * no NUL terminator, the String will comprise the entire array. + * + * @param buf The buffer containing the characters + */ + public static String toString(char[] buf) { + int len = buf.length; + for (int index = 0; index < len; index++) { + if (buf[index] == '\0') { + len = index; + break; + } } - int term = s.indexOf(0); - if (term != -1) { - s = s.substring(0, term); + + if (len == 0) { + return ""; + } else { + return new String(buf, 0, len); } - return s; } - /** Obtain a Java String from the given native wchar_t array. If there is - * no NUL terminator, the String will comprise the entire array. + /** + * Converts a "list" of strings each null terminated + * into a {@link List} of {@link String} values. The end of the + * list is signaled by an extra NULL value at the end or by the + * end of the buffer. + * @param buf The buffer containing the strings + * @return A {@link List} of all the strings in the buffer + * @see #toStringList(char[], int, int) */ - public static String toString(char[] buf) { - String s = new String(buf); - int term = s.indexOf(0); - if (term != -1) { - s = s.substring(0, term); + public static List toStringList(char[] buf) { + return toStringList(buf, 0, buf.length); + } + + /** + * Converts a "list" of strings each null terminated + * into a {@link List} of {@link String} values. The end of the + * list is signaled by an extra NULL value at the end or by the + * end of the data. + * @param buf The buffer containing the strings + * @param offset Offset to start parsing + * @param len The total characters to parse + * @return A {@link List} of all the strings in the buffer + */ + public static List toStringList(char[] buf, int offset, int len) { + List list = new ArrayList(); + int lastPos = offset; + int maxPos = offset + len; + for (int curPos = offset; curPos < maxPos; curPos++) { + if (buf[curPos] != '\0') { + continue; + } + + // check if found the extra null terminator + if (lastPos == curPos) { + return list; + } + + String value = new String(buf, lastPos, curPos - lastPos); + list.add(value); + lastPos = curPos + 1; // skip the '\0' } - return s; + + // This point is reached if there is no double null terminator + if (lastPos < maxPos) { + String value = new String(buf, lastPos, maxPos - lastPos); + list.add(value); + } + + return list; } /** Map a library interface to the current process, providing @@ -360,7 +439,7 @@ public static String toString(char[] buf) { * several locations. * @param interfaceClass * @return an instance of the requested interface, mapped to the current - * process. + * process. * @throws UnsatisfiedLinkError if the library cannot be found or * dependent libraries are missing. */ @@ -377,7 +456,7 @@ public static Object loadLibrary(Class interfaceClass) { * @param interfaceClass * @param options Map of library options * @return an instance of the requested interface, mapped to the current - * process. + * process. * @throws UnsatisfiedLinkError if the library cannot be found or * dependent libraries are missing. */ @@ -393,7 +472,7 @@ public static Object loadLibrary(Class interfaceClass, Map options) { * @param name * @param interfaceClass * @return an instance of the requested interface, mapped to the indicated - * native library. + * native library. * @throws UnsatisfiedLinkError if the library cannot be found or * dependent libraries are missing. */ @@ -412,7 +491,7 @@ public static Object loadLibrary(String name, Class interfaceClass) { * @param interfaceClass * @param options Map of library options * @return an instance of the requested interface, mapped to the indicated - * native library. + * native library. * @throws UnsatisfiedLinkError if the library cannot be found or * dependent libraries are missing. */ @@ -559,7 +638,7 @@ private static Object lookupField(Class mappingClass, String fieldName, Class re return null; } catch (Exception e) { - throw new IllegalArgumentException(fieldName + " must be a public field of type " + throw new IllegalArgumentException(fieldName + " must be a public field of type " + resultClass.getName() + " (" + e + "): " + mappingClass); } @@ -572,9 +651,9 @@ public static TypeMapper getTypeMapper(Class cls) { return (TypeMapper)getLibraryOptions(cls).get(Library.OPTION_TYPE_MAPPER); } - /** Return the preferred Strring encoding for the given native interface. + /** Return the preferred Strring encoding for the given native interface. * If there is no setting, defaults to the {@link - * #getDefaultStringEncoding()}. + * #getDefaultStringEncoding()}. * See {@link com.sun.jna.Library#OPTION_STRING_ENCODING}. */ public static String getStringEncoding(Class cls) { @@ -606,7 +685,7 @@ static byte[] getBytes(String s) { /** Return a byte array corresponding to the given String, using the given encoding. If the encoding is not found default to the platform native - encoding. + encoding. */ static byte[] getBytes(String s, String encoding) { if (encoding != null) { @@ -808,7 +887,7 @@ public static File extractFromResourcePath(String name) throws IOException { } /** Attempt to extract a native library from the resource path using the - * given class loader. + * given class loader. * @param name Base name of native library to extract. May also be an * absolute resource path (i.e. starts with "/"), in which case the * no transformations of the library name are performed. If only the base @@ -926,7 +1005,7 @@ else if (!Boolean.getBoolean("jna.nounpack")) { public static native int getLastError(); /** Set the OS last error code. The value will be saved on a per-thread - * basis. + * basis. */ public static native void setLastError(int code); @@ -951,6 +1030,7 @@ public static Library synchronizedLibrary(final Library library) { } final Library.Handler handler = (Library.Handler)ih; InvocationHandler newHandler = new InvocationHandler() { + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { synchronized(handler.getNativeLibrary()) { return handler.invoke(library, method, args); @@ -984,6 +1064,7 @@ public static String getWebStartLibraryPath(final String libName) { final ClassLoader cl = Native.class.getClassLoader(); Method m = (Method)AccessController.doPrivileged(new PrivilegedAction() { + @Override public Object run() { try { Method m = ClassLoader.class.getDeclaredMethod("findLibrary", new Class[] { String.class }); @@ -1052,6 +1133,7 @@ static File getTempDir() throws IOException { static void removeTemporaryFiles() throws IOException { File dir = getTempDir(); FilenameFilter filter = new FilenameFilter() { + @Override public boolean accept(File dir, String name) { return name.endsWith(".x") && name.startsWith(JNA_TMPLIB_PREFIX); } @@ -1200,6 +1282,7 @@ static Class findDirectMappedClass(Class cls) { */ static Class getCallingClass() { Class[] context = new SecurityManager() { + @Override public Class[] getClassContext() { return super.getClassContext(); } @@ -1397,7 +1480,7 @@ private static int getConversion(Class type, TypeMapper mapper) { * library load path or jna.library.path. * @param cls Class with native methods to register * @param libName name of or path to native library to which functions - * should be bound + * should be bound */ public static void register(Class cls, String libName) { Map options = new HashMap(); @@ -1933,8 +2016,9 @@ static Pointer getTerminationFlag(Thread t) { private static Map nativeThreads = Collections.synchronizedMap(new WeakHashMap()); - private static ThreadLocal nativeThreadTerminationFlag = + private static ThreadLocal nativeThreadTerminationFlag = new ThreadLocal() { + @Override protected Object initialValue() { Memory m = new Memory(4); m.clear(); diff --git a/test/com/sun/jna/NativeTest.java b/test/com/sun/jna/NativeTest.java index 075e0e5d21..d1252e052e 100644 --- a/test/com/sun/jna/NativeTest.java +++ b/test/com/sun/jna/NativeTest.java @@ -82,6 +82,23 @@ public void testCustomStringEncoding() throws Exception { UNICODE, Native.toString(UNICODEZ.getBytes(ENCODING), ENCODING)); } + public void testToStringList() { + List expected = Arrays.asList(getClass().getPackage().getName(), getClass().getSimpleName(), "testToStringList"); + StringBuilder sb = new StringBuilder(); + for (String value : expected) { + sb.append(value).append('\0'); + } + sb.append('\0'); + + List actual = Native.toStringList(sb.toString().toCharArray()); + assertEquals("Mismatched result size", expected.size(), actual.size()); + for (int index = 0; index < expected.size(); index++) { + String expValue = expected.get(index); + String actValue = actual.get(index); + assertEquals("Mismatched value at index #" + index, expValue, actValue); + } + } + public void testDefaultStringEncoding() throws Exception { final String UNICODE = "\u0444\u043b\u0441\u0432\u0443"; final String UNICODEZ = UNICODE + "\0more stuff";