| /* | |
| * Copyright (c) 2014, 2024, 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 | |
| } | |
| if (!GetExitCodeProcess(handle, &exitValue)) { | |
| JNU_ThrowByNameWithLastError(env, | |
| "java/lang/RuntimeException", "GetExitCodeProcess"); | |
| } else if (exitValue == STILL_ACTIVE) { | |
| if (WaitForSingleObject(handle, INFINITE) /* Wait forever */ | |
| == WAIT_FAILED) { | |
| JNU_ThrowByNameWithLastError(env, | |
| "java/lang/RuntimeException", "WaitForSingleObjects"); | |
| } else if (!GetExitCodeProcess(handle, &exitValue)) { | |
| JNU_ThrowByNameWithLastError(env, | |
| "java/lang/RuntimeException", "GetExitCodeProcess"); | |
| } | |
| } | |
| 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"); | |
| ppid = (DWORD)-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"); | |
| count = 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; | |
| 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); | |
| } |