Skip to content

Commit

Permalink
Add mapping for EnumProcesses to Psapi
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
T-Svensson committed Jun 30, 2020
1 parent d471b15 commit 3ccc9d1
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` - [@T-Svensson](https://github.com/T-Svensson/).

Bug Fixes
---------
Expand Down
57 changes: 53 additions & 4 deletions contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,49 @@ 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.
Expand All @@ -868,10 +911,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 = 0;
IntByReference lpdwSize = new IntByReference();
do {
size = size + 1024;
char[] lpExeName = new char[size];
lpdwSize.setValue(size);
if (Kernel32.INSTANCE.QueryFullProcessImageName(hProcess, dwFlags, lpExeName, lpdwSize)) {
return new String(lpExeName, 0, lpdwSize.getValue());
}
} while (Kernel32.INSTANCE.GetLastError() == Kernel32.ERROR_INSUFFICIENT_BUFFER);
throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
}

Expand Down
40 changes: 40 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Psapi.java
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,46 @@ public interface Psapi extends StdCallLibrary {
*/
boolean GetPerformanceInfo(PERFORMANCE_INFORMATION pPerformanceInformation, int cb);

/**
* Retrieves the process identifier for each process object in the system.
* <br>
* 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.
* <br>
* 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.
* <br>
* To obtain process handles for the processes whose identifiers you
* have just obtained, call the OpenProcess function.
* <br>
* Starting with Windows 7 and Windows Server 2008 R2, Psapi.h
* establishes version numbers for the PSAPI functions. The PSAPI
* version number affects the name used to call the function and the
* library that a program must load.
* <br>
* If PSAPI_VERSION is 2 or greater, this function is defined as
* K32EnumProcesses in Psapi.h and exported in Kernel32.lib and
* Kernel32.dll. If PSAPI_VERSION is 1, this function is defined as
* EnumProcesses in Psapi.h and exported in Psapi.lib and Psapi.dll as a
* wrapper that calls K32EnumProcesses.
*
* @param lpidProcess
* A pointer to an array that receives the list of process
* identifiers
* @param cb
* The size of the pProcessIds 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 <a href="https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocesses">MSDN</a>
*/
boolean EnumProcesses(int[] lpidProcess, int cb, IntByReference lpcbNeeded);

@FieldOrder({"lpBaseOfDll", "SizeOfImage", "EntryPoint"})
class MODULEINFO extends Structure {
public Pointer EntryPoint;
Expand Down
57 changes: 57 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/PsapiUtil.java
Original file line number Diff line number Diff line change
@@ -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&ouml;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 = 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
19 changes: 19 additions & 0 deletions contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

Expand Down Expand Up @@ -248,4 +249,22 @@ public void testGetPerformanceInfo() {
assertTrue(Psapi.INSTANCE.GetPerformanceInfo(perfInfo, perfInfo.size()));
assertTrue(perfInfo.ProcessCount.intValue() > 0);
}

@Test
public void testEnumProcesses() {
int myPid = Kernel32.INSTANCE.GetCurrentProcessId();
int size = 0;
int[] lpidProcess = null;
IntByReference lpcbNeeded = new IntByReference();
do {
size = 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);
assertTrue("List should contain my pid", Arrays.stream(lpidProcess, 0, lpcbNeeded.getValue() / DWORD.SIZE).anyMatch(pid -> pid == myPid));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* 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 java.util.Arrays;

import org.junit.Test;

/**
* Applies API tests on {@link PsapiUtil}.
*
* @author Torbj&ouml;rn Svensson, azoff[at]svenskalinuxforeninen.se
*/
@SuppressWarnings("nls")
public class PsapiUtilTest {
@Test
public void enumProcesses() {
int myPid = Kernel32.INSTANCE.GetCurrentProcessId();
int[] pids = PsapiUtil.enumProcesses();

assertNotNull("List should not be null", pids);
assertTrue("List should contain my pid", Arrays.stream(pids).anyMatch(pid -> pid == myPid));
}
}

0 comments on commit 3ccc9d1

Please sign in to comment.