blob: 1c45f6bf5bbbf51df2144eeaf55ee76e112a67bc [file] [log] [blame]
/*
* Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni.h"
#include "jvm.h"
#include "jni_util.h"
#include "java_lang_ProcessHandleImpl.h"
#include "java_lang_ProcessHandleImpl_Info.h"
#include <windows.h>
#include <tlhelp32.h>
#include <sddl.h>
static void getStatInfo(JNIEnv *env, HANDLE handle, jobject jinfo);
static void getCmdlineInfo(JNIEnv *env, HANDLE handle, jobject jinfo);
static void procToUser(JNIEnv *env, HANDLE handle, jobject jinfo);
/**************************************************************
* Implementation of ProcessHandleImpl_Info native methods.
*/
/* Field id for jString 'command' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_commandID;
/* Field id for jString 'commandLine' in java.lang.ProcessHandleImpl.Info */
static jfieldID ProcessHandleImpl_Info_commandLineID;
/* Field id for jString[] 'arguments' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_argumentsID;
/* Field id for jlong 'totalTime' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_totalTimeID;
/* Field id for jlong 'startTime' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_startTimeID;
/* Field id for jString 'accountName' in java.lang.ProcessHandleImpl.UserPrincipal */
static jfieldID ProcessHandleImpl_Info_userID;
/**************************************************************
* Static method to initialize field IDs.
*
* Class: java_lang_ProcessHandleImpl_Info
* Method: initIDs
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_java_lang_ProcessHandleImpl_00024Info_initIDs(JNIEnv *env, jclass clazz) {
CHECK_NULL(ProcessHandleImpl_Info_commandID = (*env)->GetFieldID(env,
clazz, "command", "Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_commandLineID = (*env)->GetFieldID(env,
clazz, "commandLine", "Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_argumentsID = (*env)->GetFieldID(env,
clazz, "arguments", "[Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_totalTimeID = (*env)->GetFieldID(env,
clazz, "totalTime", "J"));
CHECK_NULL(ProcessHandleImpl_Info_startTimeID = (*env)->GetFieldID(env,
clazz, "startTime", "J"));
CHECK_NULL(ProcessHandleImpl_Info_userID = (*env)->GetFieldID(env,
clazz, "user", "Ljava/lang/String;"));
}
/**************************************************************
* Static method to initialize native.
*
* Class: java_lang_ProcessHandleImpl
* Method: initNative
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_java_lang_ProcessHandleImpl_initNative(JNIEnv *env, jclass clazz) {
}
/*
* Block until a child process exits and return its exit code.
*/
JNIEXPORT jint JNICALL
Java_java_lang_ProcessHandleImpl_waitForProcessExit0(JNIEnv* env,
jclass junk,
jlong jpid,
jboolean reapStatus) {
DWORD pid = (DWORD)jpid;
DWORD exitValue = -1;
HANDLE handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION,
FALSE, pid);
if (handle == NULL) {
return exitValue; // No process with that pid is alive
}
do {
if (!GetExitCodeProcess(handle, &exitValue)) {
JNU_ThrowByNameWithLastError(env,
"java/lang/RuntimeException", "GetExitCodeProcess");
break;
}
if (exitValue == STILL_ACTIVE) {
HANDLE events[2];
events[0] = handle;
events[1] = JVM_GetThreadInterruptEvent();
if (WaitForMultipleObjects(sizeof(events)/sizeof(events[0]), events,
FALSE, /* Wait for ANY event */
INFINITE) /* Wait forever */
== WAIT_FAILED) {
JNU_ThrowByNameWithLastError(env,
"java/lang/RuntimeException", "WaitForMultipleObjects");
break;
}
}
} while (exitValue == STILL_ACTIVE);
CloseHandle(handle); // Ignore return code
return exitValue;
}
/*
* Returns the pid of the caller.
*
* Class: java_lang_ProcessHandleImpl
* Method: getCurrentPid0
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_java_lang_ProcessHandleImpl_getCurrentPid0(JNIEnv *env, jclass clazz) {
DWORD pid = GetCurrentProcessId();
return (jlong)pid;
}
/*
* Returns the parent pid of the requested pid.
*
* Class: java_lang_ProcessHandleImpl
* Method: parent0
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL
Java_java_lang_ProcessHandleImpl_parent0(JNIEnv *env,
jclass clazz,
jlong jpid,
jlong startTime) {
DWORD ppid = 0;
DWORD wpid = (DWORD)jpid;
PROCESSENTRY32 pe32;
HANDLE hProcessSnap;
jlong start;
start = Java_java_lang_ProcessHandleImpl_isAlive0(env, clazz, jpid);
if (start != startTime && start != 0 && startTime != 0) {
return -1;
}
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
JNU_ThrowByName(env,
"java/lang/RuntimeException", "snapshot not available");
return -1;
}
// Retrieve information about the first process,
pe32.dwSize = sizeof (PROCESSENTRY32);
if (Process32First(hProcessSnap, &pe32)) {
// Now walk the snapshot of processes, and
do {
if (wpid == pe32.th32ProcessID) {
// The parent PID may be stale if that process has exited
// and may have been reused.
// A valid parent's start time is the same or before the child's
jlong ppStartTime = Java_java_lang_ProcessHandleImpl_isAlive0(env,
clazz, pe32.th32ParentProcessID);
if (ppStartTime > 0 && ppStartTime <= startTime) {
ppid = pe32.th32ParentProcessID;
}
break;
}
} while (Process32Next(hProcessSnap, &pe32));
} else {
JNU_ThrowByName(env,
"java/lang/RuntimeException", "snapshot not available");
return -1;
}
CloseHandle(hProcessSnap); // Ignore return code
return (jlong)ppid;
}
/*
* Returns the children of the requested pid and optionally each parent.
*
* Class: java_lang_ProcessHandleImpl
* Method: getChildPids
* Signature: (J[J[J)I
*/
JNIEXPORT jint JNICALL
Java_java_lang_ProcessHandleImpl_getProcessPids0(JNIEnv *env,
jclass clazz,
jlong jpid,
jlongArray jarray,
jlongArray jparentArray,
jlongArray jstimesArray) {
HANDLE hProcessSnap;
PROCESSENTRY32 pe32;
DWORD ppid = (DWORD)jpid;
jlong* pids = NULL;
jlong* ppids = NULL;
jlong* stimes = NULL;
jsize parentArraySize = 0;
jsize arraySize = 0;
jsize stimesSize = 0;
jsize count = 0;
arraySize = (*env)->GetArrayLength(env, jarray);
JNU_CHECK_EXCEPTION_RETURN(env, -1);
if (jparentArray != NULL) {
parentArraySize = (*env)->GetArrayLength(env, jparentArray);
JNU_CHECK_EXCEPTION_RETURN(env, -1);
if (arraySize != parentArraySize) {
JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
return 0;
}
}
if (jstimesArray != NULL) {
stimesSize = (*env)->GetArrayLength(env, jstimesArray);
JNU_CHECK_EXCEPTION_RETURN(env, -1);
if (arraySize != stimesSize) {
JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
return 0;
}
}
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
JNU_ThrowByName(env,
"java/lang/RuntimeException", "snapshot not available");
return 0;
}
// Retrieve information about the first process,
pe32.dwSize = sizeof (PROCESSENTRY32);
if (Process32First(hProcessSnap, &pe32)) {
do { // Block to break out of on Exception
pids = (*env)->GetLongArrayElements(env, jarray, NULL);
if (pids == NULL) {
break;
}
if (jparentArray != NULL) {
ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL);
if (ppids == NULL) {
break;
}
}
if (jstimesArray != NULL) {
stimes = (*env)->GetLongArrayElements(env, jstimesArray, NULL);
if (stimes == NULL) {
break;
}
}
// Now walk the snapshot of processes, and
// save information about each process in turn
do {
if (ppid == 0 ||
(pe32.th32ParentProcessID > 0
&& (pe32.th32ParentProcessID == ppid))) {
if (count < arraySize) {
// Only store if it fits
pids[count] = (jlong) pe32.th32ProcessID;
if (ppids != NULL) {
// Store the parentPid
ppids[count] = (jlong) pe32.th32ParentProcessID;
}
if (stimes != NULL) {
// Store the process start time
stimes[count] =
Java_java_lang_ProcessHandleImpl_isAlive0(env,
clazz, (jlong) pe32.th32ProcessID);
}
}
count++; // Count to tabulate size needed
}
} while (Process32Next(hProcessSnap, &pe32));
} while (0);
if (pids != NULL) {
(*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
}
if (ppids != NULL) {
(*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
}
if (stimes != NULL) {
(*env)->ReleaseLongArrayElements(env, jstimesArray, stimes, 0);
}
} else {
JNU_ThrowByName(env,
"java/lang/RuntimeException", "snapshot not available");
return 0;
}
CloseHandle(hProcessSnap);
// If more pids than array had size for; count will be greater than array size
return (jint)count;
}
/**
* Assemble a 64 bit value from two 32 bit values.
*/
static jlong jlong_from(jint high, jint low) {
jlong result = 0;
result = ((jlong)high << 32) | ((0x000000000ffffffff) & (jlong)low);
return result;
}
/*
* Get the start time in ms from 1970 from the handle.
*/
static jlong getStartTime(HANDLE handle) {
FILETIME CreationTime, ExitTime, KernelTime, UserTime;
if (GetProcessTimes(handle, &CreationTime, &ExitTime, &KernelTime, &UserTime)) {
jlong start = jlong_from(CreationTime.dwHighDateTime,
CreationTime.dwLowDateTime) / 10000;
start -= 11644473600000L; // Rebase Epoch from 1601 to 1970
return start;
} else {
return 0;
}
}
/*
* Destroy the process.
*
* Class: java_lang_ProcessHandleImpl
* Method: destroy0
* Signature: (Z)V
*/
JNIEXPORT jboolean JNICALL
Java_java_lang_ProcessHandleImpl_destroy0(JNIEnv *env,
jclass clazz,
jlong jpid,
jlong startTime,
jboolean force) {
DWORD pid = (DWORD)jpid;
jboolean ret = JNI_FALSE;
HANDLE handle = OpenProcess(PROCESS_TERMINATE | THREAD_QUERY_INFORMATION
| PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (handle != NULL) {
jlong start = getStartTime(handle);
if (start == startTime || startTime == 0) {
ret = TerminateProcess(handle, 1) ? JNI_TRUE : JNI_FALSE;
}
CloseHandle(handle); // Ignore return code
}
return ret;
}
/*
* Check if a process is alive.
* Return the start time (ms since 1970) if it is available.
* If the start time is not available return 0.
* If the pid is invalid, return -1.
*
* Class: java_lang_ProcessHandleImpl
* Method: isAlive0
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL
Java_java_lang_ProcessHandleImpl_isAlive0(JNIEnv *env, jclass clazz, jlong jpid) {
DWORD pid = (DWORD)jpid;
jlong ret = -1;
HANDLE handle =
OpenProcess(THREAD_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION,
FALSE, pid);
if (handle != NULL) {
DWORD dwExitStatus;
GetExitCodeProcess(handle, &dwExitStatus);
if (dwExitStatus == STILL_ACTIVE) {
ret = getStartTime(handle);
} else {
ret = -1;
}
CloseHandle(handle); // Ignore return code
}
return ret;
}
/*
* Fill in the Info object from the OS information about the process.
*
* Class: java_lang_ProcessHandleImpl
* Method: info0
* Signature: (J)V
*/
JNIEXPORT void JNICALL
Java_java_lang_ProcessHandleImpl_00024Info_info0(JNIEnv *env,
jobject jinfo,
jlong jpid) {
DWORD pid = (DWORD)jpid;
int ret = 0;
HANDLE handle =
OpenProcess(THREAD_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION,
FALSE, pid);
if (handle == NULL) {
return;
}
getStatInfo(env, handle, jinfo);
getCmdlineInfo(env, handle, jinfo);
procToUser(env, handle, jinfo);
CloseHandle(handle); // Ignore return code
}
/**
* Read /proc/<pid>/stat and fill in the fields of the Info object.
* The executable name, plus the user, system, and start times are gathered.
*/
static void getStatInfo(JNIEnv *env, HANDLE handle, jobject jinfo) {
FILETIME CreationTime;
FILETIME ExitTime;
FILETIME KernelTime;
FILETIME UserTime;
jlong userTime; // nanoseconds
jlong totalTime; // nanoseconds
jlong startTime; // nanoseconds
UserTime.dwHighDateTime = 0;
UserTime.dwLowDateTime = 0;
KernelTime.dwHighDateTime = 0;
KernelTime.dwLowDateTime = 0;
CreationTime.dwHighDateTime = 0;
CreationTime.dwLowDateTime = 0;
if (GetProcessTimes(handle, &CreationTime, &ExitTime, &KernelTime, &UserTime)) {
userTime = jlong_from(UserTime.dwHighDateTime, UserTime.dwLowDateTime);
totalTime = jlong_from( KernelTime.dwHighDateTime, KernelTime.dwLowDateTime);
totalTime = (totalTime + userTime) * 100; // convert sum to nano-seconds
startTime = jlong_from(CreationTime.dwHighDateTime,
CreationTime.dwLowDateTime) / 10000;
startTime -= 11644473600000L; // Rebase Epoch from 1601 to 1970
(*env)->SetLongField(env, jinfo,
ProcessHandleImpl_Info_totalTimeID, totalTime);
JNU_CHECK_EXCEPTION(env);
(*env)->SetLongField(env, jinfo,
ProcessHandleImpl_Info_startTimeID, startTime);
JNU_CHECK_EXCEPTION(env);
}
}
static void getCmdlineInfo(JNIEnv *env, HANDLE handle, jobject jinfo) {
WCHAR exeName[1024];
WCHAR *longPath;
DWORD bufsize = sizeof(exeName)/sizeof(WCHAR);
jstring commandObj = NULL;
if (QueryFullProcessImageNameW(handle, 0, exeName, &bufsize)) {
commandObj = (*env)->NewString(env, (const jchar *)exeName,
(jsize)wcslen(exeName));
} else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
bufsize = 32768;
longPath = (WCHAR*)malloc(bufsize * sizeof(WCHAR));
if (longPath != NULL) {
if (QueryFullProcessImageNameW(handle, 0, longPath, &bufsize)) {
commandObj = (*env)->NewString(env, (const jchar *)longPath,
(jsize)wcslen(longPath));
}
free(longPath);
}
}
CHECK_NULL(commandObj);
(*env)->SetObjectField(env, jinfo,
ProcessHandleImpl_Info_commandID, commandObj);
}
static void procToUser(JNIEnv *env, HANDLE handle, jobject jinfo) {
#define TOKEN_LEN 256
DWORD token_len = TOKEN_LEN;
char token_buf[TOKEN_LEN];
TOKEN_USER *token_user = (TOKEN_USER*)token_buf;
HANDLE tokenHandle;
WCHAR domain[255 + 1 + 255 + 1]; // large enough to concat with '/' and name
WCHAR name[255 + 1];
DWORD domainLen = sizeof(domain) - sizeof(name);
DWORD nameLen = sizeof(name);
SID_NAME_USE use;
jstring s;
int ret;
if (!OpenProcessToken(handle, TOKEN_READ, &tokenHandle)) {
return;
}
ret = GetTokenInformation(tokenHandle, TokenUser, token_user,
token_len, &token_len);
CloseHandle(tokenHandle); // always close handle
if (!ret) {
JNU_ThrowByNameWithLastError(env,
"java/lang/RuntimeException", "GetTokenInformation");
return;
}
if (LookupAccountSidW(NULL, token_user->User.Sid, &name[0], &nameLen,
&domain[0], &domainLen, &use) == 0) {
// Name not available, convert to a String
LPWSTR str;
if (ConvertSidToStringSidW(token_user->User.Sid, &str) == 0) {
return;
}
s = (*env)->NewString(env, (const jchar *)str, (jsize)wcslen(str));
LocalFree(str);
} else {
wcscat(domain, L"\\");
wcscat(domain, name);
s = (*env)->NewString(env, (const jchar *)domain, (jsize)wcslen(domain));
}
CHECK_NULL(s);
(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, s);
}