From a49feec88cbc89142d757f6dd2682a515988c03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Svensson?= Date: Fri, 26 Jun 2020 21:37:57 +0200 Subject: [PATCH] Add mapping for EnumProcesses to Psapi Added utility method QueryFullProcessImageName that takes a PID. Fixed QueryFullProcessImageName to properly handle paths longer than 260 characters that can happen on Win10 / paths with UNC. --- CHANGES.md | 1 + .../sun/jna/platform/win32/Kernel32Util.java | 55 ++++++++++++++++-- .../src/com/sun/jna/platform/win32/Psapi.java | 28 ++++++++- .../com/sun/jna/platform/win32/PsapiUtil.java | 57 +++++++++++++++++++ .../jna/platform/win32/Kernel32UtilTest.java | 16 +++++- .../com/sun/jna/platform/win32/PsapiTest.java | 25 ++++++++ .../sun/jna/platform/win32/PsapiUtilTest.java | 53 +++++++++++++++++ 7 files changed, 228 insertions(+), 7 deletions(-) create mode 100644 contrib/platform/src/com/sun/jna/platform/win32/PsapiUtil.java create mode 100644 contrib/platform/test/com/sun/jna/platform/win32/PsapiUtilTest.java diff --git a/CHANGES.md b/CHANGES.md index 09ebd6b588..7ceca9538d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,7 @@ Features * [#1200](https://github.com/java-native-access/jna/pull/1200): Add mappings for `libudev` to `c.s.j.p.linux.Udev` - [@dbwiddis](https://github.com/dbwiddis). * [#1202](https://github.com/java-native-access/jna/pull/1202): Add mappings supporting shared memory including `c.s.j.p.unix.LibCAPI` types `size_t` and `ssize_t`, `c.s.j.p.linux.LibC` methods `munmap()`, `msync()`, and `close()`, `c.s.j.p.unix.LibCUtil` mapping `mmap()` and `ftruncate()`, and `c.s.j.p.linux.LibRT` methods `shm_open()` and `shm_unlink()` - [@dbwiddis](https://github.com/dbwiddis). * [#1209](https://github.com/java-native-access/jna/pull/1209): Add mappings for `Thread32First` and `Thread32Next` to `c.s.j.p.win32.Kernel32` - [@dbwiddis](https://github.com/dbwiddis). +* [#1214](https://github.com/java-native-access/jna/pull/1214): Add mapping for EnumProcesses to `c.s.j.p.win32.Psapi` and `c.s.j.p.win32.PsapiUtil` - [@T-Svensson](https://github.com/T-Svensson/). Bug Fixes --------- 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 d4a5394e83..175690c256 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java @@ -854,6 +854,47 @@ public static final String extractVolumeGUID(String volumeGUIDPath) { return volumeGUIDPath.substring(VOLUME_GUID_PATH_PREFIX.length(), volumeGUIDPath.length() - VOLUME_GUID_PATH_SUFFIX.length()); } + /** + * This function retrieves the full path of the executable file of a given process identifier. + * + * @param pid + * Identifier for the running process + * @param dwFlags + * 0 - The name should use the Win32 path format. + * 1(WinNT.PROCESS_NAME_NATIVE) - The name should use the native system path format. + * + * @return the full path of the process's executable file of null if failed. To get extended error information, + * call GetLastError. + */ + public static final String QueryFullProcessImageName(int pid, int dwFlags) { + HANDLE hProcess = null; + Win32Exception we = null; + + try { + hProcess = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION | WinNT.PROCESS_VM_READ, false, pid); + if (hProcess == null) { + throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); + } + return QueryFullProcessImageName(hProcess, dwFlags); + } catch (Win32Exception e) { + we = e; + throw we; // re-throw to invoke finally block + } finally { + try { + closeHandle(hProcess); + } catch (Win32Exception e) { + if (we == null) { + we = e; + } else { + we.addSuppressed(e); + } + } + if (we != null) { + throw we; + } + } + } + /** * * This function retrieves the full path of the executable file of a given process. @@ -868,10 +909,16 @@ public static final String extractVolumeGUID(String volumeGUIDPath) { * call GetLastError. */ public static final String QueryFullProcessImageName(HANDLE hProcess, int dwFlags) { - char[] path = new char[WinDef.MAX_PATH]; - IntByReference lpdwSize = new IntByReference(path.length); - if (Kernel32.INSTANCE.QueryFullProcessImageName(hProcess, 0, path, lpdwSize)) - return new String(path).substring(0, lpdwSize.getValue()); + int size = WinDef.MAX_PATH; // Start with MAX_PATH, then increment with 1024 each iteration + IntByReference lpdwSize = new IntByReference(); + do { + char[] lpExeName = new char[size]; + lpdwSize.setValue(size); + if (Kernel32.INSTANCE.QueryFullProcessImageName(hProcess, dwFlags, lpExeName, lpdwSize)) { + return new String(lpExeName, 0, lpdwSize.getValue()); + } + size += 1024; + } while (Kernel32.INSTANCE.GetLastError() == Kernel32.ERROR_INSUFFICIENT_BUFFER); throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java b/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java index c6c89c5180..0d5b662af2 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java @@ -23,8 +23,6 @@ */ package com.sun.jna.platform.win32; -import java.util.List; - import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.Structure; @@ -266,6 +264,32 @@ public interface Psapi extends StdCallLibrary { */ boolean GetPerformanceInfo(PERFORMANCE_INFORMATION pPerformanceInformation, int cb); + /** + * Retrieves the process identifier for each process object in the system.
+ * It is a good idea to use a large array, because it is hard to predict how + * many processes there will be at the time you call EnumProcesses.
+ * To determine how many processes were enumerated, divide the pBytesReturned + * value by sizeof(DWORD). There is no indication given when the buffer is too + * small to store all process identifiers. Therefore, if pBytesReturned equals + * cb, consider retrying the call with a larger array.
+ * To obtain process handles for the processes whose identifiers you have just + * obtained, call the OpenProcess function. + * + * @param lpidProcess + * A pointer to an array that receives the list of process + * identifiers + * @param cb + * The size of the lpidProcess array, in bytes. + * @param lpcbNeeded + * The number of bytes returned in the pProcessIds array. + * @return If the function succeeds, the return value is nonzero. If the + * function fails, the return value is zero. To get extended error + * information, call GetLastError. + * @see MSDN + */ + boolean EnumProcesses(int[] lpidProcess, int cb, IntByReference lpcbNeeded); + @FieldOrder({"lpBaseOfDll", "SizeOfImage", "EntryPoint"}) class MODULEINFO extends Structure { public Pointer EntryPoint; diff --git a/contrib/platform/src/com/sun/jna/platform/win32/PsapiUtil.java b/contrib/platform/src/com/sun/jna/platform/win32/PsapiUtil.java new file mode 100644 index 0000000000..ca293ce0e9 --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/win32/PsapiUtil.java @@ -0,0 +1,57 @@ +/* Copyright (c) 2020 Torbjörn Svensson, All Rights Reserved + * + * 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.win32; + +import java.util.Arrays; + +import com.sun.jna.platform.win32.WinDef.DWORD; +import com.sun.jna.ptr.IntByReference; + +/** + * Psapi utility API. + * + * @author Torbjörn Svensson, azoff[at]svenskalinuxforeninen.se + */ +public abstract class PsapiUtil { + + /** + * Retrieves the process identifier for each process object in the system. + * + * @return Array of pids + */ + public static int[] enumProcesses() { + int size = 0; + int[] lpidProcess = null; + IntByReference lpcbNeeded = new IntByReference(); + do { + size += 1024; + lpidProcess = new int[size]; + if (!Psapi.INSTANCE.EnumProcesses(lpidProcess, size * DWORD.SIZE, lpcbNeeded)) { + throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); + } + } while (size == lpcbNeeded.getValue() / DWORD.SIZE); + + return Arrays.copyOf(lpidProcess, lpcbNeeded.getValue() / DWORD.SIZE); + } +} 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 20684a07dc..5d326eace7 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java @@ -313,14 +313,28 @@ public final void testWritePrivateProfileSection() throws IOException { } public final void testQueryFullProcessImageName() { - HANDLE h = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, Kernel32.INSTANCE.GetCurrentProcessId()); + int pid = Kernel32.INSTANCE.GetCurrentProcessId(); + + HANDLE h = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, pid); assertNotNull("Failed (" + Kernel32.INSTANCE.GetLastError() + ") to get process handle", h); try { String name = Kernel32Util.QueryFullProcessImageName(h, 0); + assertNotNull("Failed to query process image name, null path returned", name); assertTrue("Failed to query process image name, empty path returned", name.length() > 0); } finally { Kernel32Util.closeHandle(h); } + + String name = Kernel32Util.QueryFullProcessImageName(pid, 0); + assertNotNull("Failed to query process image name, null path returned", name); + assertTrue("Failed to query process image name, empty path returned", name.length() > 0); + + try { + Kernel32Util.QueryFullProcessImageName(0, 0); // the system process + fail("Should never reach here"); + } catch (Win32Exception expected) { + assertEquals("Should get Invalid Parameter error", Kernel32.ERROR_INVALID_PARAMETER, expected.getErrorCode()); + } } public void testGetResource() { diff --git a/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java b/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java index 44404e896b..7af0c9b275 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java @@ -248,4 +248,29 @@ public void testGetPerformanceInfo() { assertTrue(Psapi.INSTANCE.GetPerformanceInfo(perfInfo, perfInfo.size())); assertTrue(perfInfo.ProcessCount.intValue() > 0); } + + @Test + public void testEnumProcesses() { + int size = 0; + int[] lpidProcess = null; + IntByReference lpcbNeeded = new IntByReference(); + do { + size += 1024; + lpidProcess = new int[size]; + if (!Psapi.INSTANCE.EnumProcesses(lpidProcess, size * DWORD.SIZE, lpcbNeeded)) { + throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); + } + } while (size == lpcbNeeded.getValue() / DWORD.SIZE); + assertTrue("Size of pid list in bytes should be a multiple of " + DWORD.SIZE, lpcbNeeded.getValue() % DWORD.SIZE == 0); + + int myPid = Kernel32.INSTANCE.GetCurrentProcessId(); + boolean foundMyPid = false; + for (int i = 0; i < lpcbNeeded.getValue() / DWORD.SIZE; i++) { + if (lpidProcess[i] == myPid) { + foundMyPid = true; + break; + } + } + assertTrue("List should contain my pid", foundMyPid); + } } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/PsapiUtilTest.java b/contrib/platform/test/com/sun/jna/platform/win32/PsapiUtilTest.java new file mode 100644 index 0000000000..eba02c1498 --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/win32/PsapiUtilTest.java @@ -0,0 +1,53 @@ +/* Copyright (c) 2020 Torbjörn Svensson, All Rights Reserved + * + * 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.win32; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Applies API tests on {@link PsapiUtil}. + * + * @author Torbjörn Svensson, azoff[at]svenskalinuxforeninen.se + */ +@SuppressWarnings("nls") +public class PsapiUtilTest { + @Test + public void enumProcesses() { + int[] pids = PsapiUtil.enumProcesses(); + assertNotNull("List should not be null", pids); + + int myPid = Kernel32.INSTANCE.GetCurrentProcessId(); + boolean foundMyPid = false; + for (int i = 0; i < pids.length; i++) { + if (pids[i] == myPid) { + foundMyPid = true; + break; + } + } + assertTrue("List should contain my pid", foundMyPid); + } +}