| /* | |
| * Copyright (c) 1997, 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 <assert.h> | |
| #include "java_lang_ProcessImpl.h" | |
| #include "jni.h" | |
| #include "jvm.h" | |
| #include "jni_util.h" | |
| #include "io_util.h" | |
| #include "io_util_md.h" | |
| #include <windows.h> | |
| #include <io.h> | |
| #include <wchar.h> | |
| /* We try to make sure that we can read and write 4095 bytes (the | |
| * fixed limit on Linux) to the pipe on all operating systems without | |
| * deadlock. Windows 2000 inexplicably appears to need an extra 24 | |
| * bytes of slop to avoid deadlock. | |
| */ | |
| #define PIPE_SIZE (4096+24) | |
| /* We have THREE locales in action: | |
| * 1. Thread default locale - dictates UNICODE-to-8bit conversion | |
| * 2. System locale that defines the message localization | |
| * 3. The file name locale | |
| * Each locale could be an extended locale, that means that text cannot be | |
| * mapped to 8bit sequence without multibyte encoding. | |
| * VM is ready for that, if text is UTF-8. | |
| * Here we make the work right from the beginning. | |
| */ | |
| size_t os_error_message(int errnum, WCHAR* utf16_OSErrorMsg, size_t maxMsgLength) { | |
| size_t n = (size_t)FormatMessageW( | |
| FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, | |
| NULL, | |
| (DWORD)errnum, | |
| 0, | |
| utf16_OSErrorMsg, | |
| (DWORD)maxMsgLength, | |
| NULL); | |
| if (n > 3) { | |
| // Drop final '.', CR, LF | |
| if (utf16_OSErrorMsg[n - 1] == L'\n') --n; | |
| if (utf16_OSErrorMsg[n - 1] == L'\r') --n; | |
| if (utf16_OSErrorMsg[n - 1] == L'.') --n; | |
| utf16_OSErrorMsg[n] = L'\0'; | |
| } | |
| return n; | |
| } | |
| #define MESSAGE_LENGTH (256 + 100) | |
| #define ARRAY_SIZE(x) (sizeof(x)/sizeof(*x)) | |
| static void | |
| win32Error(JNIEnv *env, const WCHAR *functionName) | |
| { | |
| WCHAR utf16_OSErrorMsg[MESSAGE_LENGTH - 100]; | |
| WCHAR utf16_javaMessage[MESSAGE_LENGTH]; | |
| /*Good suggestion about 2-bytes-per-symbol in localized error reports*/ | |
| char utf8_javaMessage[MESSAGE_LENGTH*2]; | |
| const int errnum = (int)GetLastError(); | |
| size_t n = os_error_message(errnum, utf16_OSErrorMsg, ARRAY_SIZE(utf16_OSErrorMsg)); | |
| n = (n > 0) | |
| ? swprintf(utf16_javaMessage, MESSAGE_LENGTH, L"%s error=%d, %s", functionName, errnum, utf16_OSErrorMsg) | |
| : swprintf(utf16_javaMessage, MESSAGE_LENGTH, L"%s failed, error=%d", functionName, errnum); | |
| if (n > 0) /*terminate '\0' is not a part of conversion procedure*/ | |
| n = WideCharToMultiByte( | |
| CP_UTF8, | |
| 0, | |
| utf16_javaMessage, | |
| (int)n, /*by creation n <= MESSAGE_LENGTH*/ | |
| utf8_javaMessage, | |
| MESSAGE_LENGTH*2, | |
| NULL, | |
| NULL); | |
| /*no way to die*/ | |
| { | |
| const char *errorMessage = "Secondary error while OS message extraction"; | |
| if (n > 0) { | |
| utf8_javaMessage[min(MESSAGE_LENGTH*2 - 1, n)] = '\0'; | |
| errorMessage = utf8_javaMessage; | |
| } | |
| JNU_ThrowIOException(env, errorMessage); | |
| } | |
| } | |
| static void | |
| closeSafely(HANDLE handle) | |
| { | |
| if (handle != INVALID_HANDLE_VALUE) | |
| CloseHandle(handle); | |
| } | |
| static BOOL hasInheritFlag(HANDLE handle) | |
| { | |
| DWORD mask; | |
| if (GetHandleInformation(handle, &mask)) { | |
| return mask & HANDLE_FLAG_INHERIT; | |
| } | |
| return FALSE; | |
| } | |
| #define HANDLE_STORAGE_SIZE 6 | |
| #define OFFSET_READ 0 | |
| #define OFFSET_WRITE 1 | |
| //long signed version of INVALID_HANDLE_VALUE | |
| #define JAVA_INVALID_HANDLE_VALUE ((jlong) -1) | |
| #define OPPOSITE_END(offset) (offset==OFFSET_READ ? OFFSET_WRITE : OFFSET_READ) | |
| /* Pipe holder structure */ | |
| typedef struct _STDHOLDER { | |
| HANDLE pipe[2]; | |
| int offset; | |
| } STDHOLDER; | |
| /* Responsible for correct initialization of the [pHolder] structure | |
| (that is used for handles recycling) if needs, | |
| and appropriate setup of IOE handle [phStd] for child process based | |
| on created pipe or Java handle. */ | |
| static BOOL initHolder( | |
| JNIEnv *env, | |
| jlong *pjhandles, /* IN OUT - the handle form Java, | |
| that can be a file, console or undefined */ | |
| STDHOLDER *pHolder, /* OUT - initialized structure that holds pipe | |
| handles */ | |
| HANDLE *phStd /* OUT - initialized handle for child process */ | |
| ) { | |
| /* Here we test the value from Java against invalid | |
| handle value. We are not using INVALID_HANDLE_VALUE macro | |
| due to double signed/unsigned and 32/64bit ambiguity. | |
| Otherwise it will be easy to get the wrong | |
| value 0x00000000FFFFFFFF | |
| instead 0xFFFFFFFFFFFFFFFF. */ | |
| if (*pjhandles != JAVA_INVALID_HANDLE_VALUE) { | |
| /* Java file or console redirection */ | |
| *phStd = (HANDLE) *pjhandles; | |
| /* Here we set the related Java stream (Process.getXXXXStream()) | |
| to [ProcessBuilder.NullXXXXStream.INSTANCE] value. | |
| The initial Java handle [*pjhandles] will be closed in | |
| ANY case. It is not a handle leak. */ | |
| *pjhandles = JAVA_INVALID_HANDLE_VALUE; | |
| } else { | |
| /* Creation of parent-child pipe */ | |
| if (!CreatePipe( | |
| &pHolder->pipe[OFFSET_READ], | |
| &pHolder->pipe[OFFSET_WRITE], | |
| NULL, /* we would like to inherit | |
| default process access, | |
| instead of 'Everybody' access */ | |
| PIPE_SIZE)) | |
| { | |
| win32Error(env, L"CreatePipe"); | |
| return FALSE; | |
| } else { | |
| /* [thisProcessEnd] has no the inherit flag because | |
| the [lpPipeAttributes] param of [CreatePipe] | |
| had the NULL value. */ | |
| HANDLE thisProcessEnd = pHolder->pipe[OPPOSITE_END(pHolder->offset)]; | |
| *phStd = pHolder->pipe[pHolder->offset]; | |
| *pjhandles = (jlong) thisProcessEnd; | |
| } | |
| } | |
| /* Pipe handle will be closed in the [releaseHolder] call, | |
| file handle will be closed in Java. | |
| The long-live handle need to restore the inherit flag, | |
| we do it later in the [prepareIOEHandleState] call. */ | |
| SetHandleInformation( | |
| *phStd, | |
| HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); | |
| return TRUE; | |
| } | |
| /* Smart recycling of pipe handles in [pHolder]. For the failed | |
| create process attempts, both ends of pipe need to be released. | |
| The [complete] has the [TRUE] value in the failed attempt. */ | |
| static void releaseHolder(BOOL complete, STDHOLDER *pHolder) { | |
| closeSafely(pHolder->pipe[pHolder->offset]); | |
| if (complete) { | |
| /* Error occur, close this process pipe end */ | |
| closeSafely(pHolder->pipe[OPPOSITE_END(pHolder->offset)]); | |
| } | |
| } | |
| /* Stores and drops the inherit flag of handles that should not | |
| be shared with the child process by default, but can hold the | |
| inherit flag due to MS process birth specific. */ | |
| static void prepareIOEHandleState( | |
| HANDLE *stdIOE, | |
| BOOL *inherit) | |
| { | |
| int i; | |
| for (i = 0; i < HANDLE_STORAGE_SIZE; ++i) { | |
| HANDLE hstd = stdIOE[i]; | |
| if (INVALID_HANDLE_VALUE != hstd && hasInheritFlag(hstd)) { | |
| /* FALSE by default */ | |
| inherit[i] = TRUE; | |
| /* Java does not need implicit inheritance for IOE handles, | |
| so we drop inherit flag that probably was installed by | |
| previous CreateProcess call that launched current process. | |
| We will return the handle state back after CreateProcess call. | |
| By clearing inherit flag we prevent "greedy grandchild" birth. | |
| The explicit inheritance for child process IOE handles is | |
| implemented in the [initHolder] call. */ | |
| SetHandleInformation(hstd, HANDLE_FLAG_INHERIT, 0); | |
| } | |
| } | |
| } | |
| /* Restores the inheritance flag of handles from stored values. */ | |
| static void restoreIOEHandleState( | |
| const HANDLE *stdIOE, | |
| const BOOL *inherit) | |
| { | |
| /* The set of current process standard IOE handles and | |
| the set of child process IOE handles can intersect. | |
| To restore the inherit flag right, we use backward | |
| array iteration. */ | |
| int i; | |
| for (i = HANDLE_STORAGE_SIZE - 1; i >= 0; --i) | |
| if (INVALID_HANDLE_VALUE != stdIOE[i]) { | |
| /* Restore inherit flag for any case. | |
| The handle can be changed by explicit inheritance.*/ | |
| SetHandleInformation(stdIOE[i], | |
| HANDLE_FLAG_INHERIT, | |
| inherit[i] ? HANDLE_FLAG_INHERIT : 0); | |
| } | |
| } | |
| /* | |
| * Class: java_lang_ProcessImpl | |
| * Method: getProcessId0 | |
| * Signature: (J)I | |
| */ | |
| JNIEXPORT jint JNICALL Java_java_lang_ProcessImpl_getProcessId0 | |
| (JNIEnv *env, jclass clazz, jlong handle) { | |
| DWORD pid = GetProcessId((HANDLE) jlong_to_ptr(handle)); | |
| return (jint)pid; | |
| } | |
| /* Please, read about the MS inheritance problem | |
| http://support.microsoft.com/kb/315939 | |
| and critical section/synchronized block solution. */ | |
| static jlong processCreate( | |
| JNIEnv *env, | |
| const jchar *pcmd, | |
| const jchar *penvBlock, | |
| const jchar *pdir, | |
| jlong *handles, | |
| jboolean redirectErrorStream) | |
| { | |
| jlong ret = 0L; | |
| STARTUPINFOW si = {sizeof(si)}; | |
| /* Handles for which the inheritance flag must be restored. */ | |
| HANDLE stdIOE[HANDLE_STORAGE_SIZE] = { | |
| /* Current process standard IOE handles: JDK-7147084 */ | |
| INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, | |
| /* Child process IOE handles: JDK-6921885 */ | |
| (HANDLE)handles[0], (HANDLE)handles[1], (HANDLE)handles[2]}; | |
| BOOL inherit[HANDLE_STORAGE_SIZE] = { | |
| FALSE, FALSE, FALSE, | |
| FALSE, FALSE, FALSE}; | |
| /* These three should not be closed by CloseHandle! */ | |
| stdIOE[0] = GetStdHandle(STD_INPUT_HANDLE); | |
| stdIOE[1] = GetStdHandle(STD_OUTPUT_HANDLE); | |
| stdIOE[2] = GetStdHandle(STD_ERROR_HANDLE); | |
| prepareIOEHandleState(stdIOE, inherit); | |
| { | |
| /* Input */ | |
| STDHOLDER holderIn = {{INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}, OFFSET_READ}; | |
| if (initHolder(env, &handles[0], &holderIn, &si.hStdInput)) { | |
| /* Output */ | |
| STDHOLDER holderOut = {{INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}, OFFSET_WRITE}; | |
| if (initHolder(env, &handles[1], &holderOut, &si.hStdOutput)) { | |
| /* Error */ | |
| STDHOLDER holderErr = {{INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}, OFFSET_WRITE}; | |
| BOOL success; | |
| if (redirectErrorStream) { | |
| si.hStdError = si.hStdOutput; | |
| /* Here we set the error stream to [ProcessBuilder.NullInputStream.INSTANCE] | |
| value. That is in accordance with Java Doc for the redirection case. | |
| The Java file for the [ handles[2] ] will be closed in ANY case. It is not | |
| a handle leak. */ | |
| handles[2] = JAVA_INVALID_HANDLE_VALUE; | |
| success = TRUE; | |
| } else { | |
| success = initHolder(env, &handles[2], &holderErr, &si.hStdError); | |
| } | |
| if (success) { | |
| PROCESS_INFORMATION pi; | |
| DWORD processFlag = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT; | |
| /* If the standard I/O is inherited, CREATE_NO_WINDOW must not be used. */ | |
| if (GetConsoleWindow() != NULL && | |
| (si.hStdInput == stdIOE[0] || | |
| si.hStdOutput == stdIOE[1] || | |
| si.hStdError == (redirectErrorStream ? stdIOE[1] : stdIOE[2]))) | |
| { | |
| processFlag &= ~CREATE_NO_WINDOW; | |
| } | |
| si.dwFlags = STARTF_USESTDHANDLES; | |
| if (!CreateProcessW( | |
| NULL, /* executable name */ | |
| (LPWSTR)pcmd, /* command line */ | |
| NULL, /* process security attribute */ | |
| NULL, /* thread security attribute */ | |
| TRUE, /* inherits system handles */ | |
| processFlag, /* selected based on exe type */ | |
| (LPVOID)penvBlock,/* environment block */ | |
| (LPCWSTR)pdir, /* change to the new current directory */ | |
| &si, /* (in) startup information */ | |
| &pi)) /* (out) process information */ | |
| { | |
| win32Error(env, L"CreateProcess"); | |
| } else { | |
| closeSafely(pi.hThread); | |
| ret = (jlong)pi.hProcess; | |
| } | |
| } | |
| releaseHolder(ret == 0, &holderErr); | |
| releaseHolder(ret == 0, &holderOut); | |
| } | |
| releaseHolder(ret == 0, &holderIn); | |
| } | |
| } | |
| restoreIOEHandleState(stdIOE, inherit); | |
| return ret; | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored, | |
| jstring cmd, | |
| jstring envBlock, | |
| jstring dir, | |
| jlongArray stdHandles, | |
| jboolean redirectErrorStream) | |
| { | |
| jlong ret = 0; | |
| if (cmd != NULL && stdHandles != NULL) { | |
| const jchar *pcmd = (*env)->GetStringChars(env, cmd, NULL); | |
| if (pcmd != NULL) { | |
| const jchar *penvBlock = (envBlock != NULL) | |
| ? (*env)->GetStringChars(env, envBlock, NULL) | |
| : NULL; | |
| if (!(*env)->ExceptionCheck(env)) { | |
| const jchar *pdir = (dir != NULL) | |
| ? (*env)->GetStringChars(env, dir, NULL) | |
| : NULL; | |
| if (!(*env)->ExceptionCheck(env)) { | |
| jlong *handles = (*env)->GetLongArrayElements(env, stdHandles, NULL); | |
| if (handles != NULL) { | |
| // Copy command line to mutable char buffer; CreateProcessW may modify it | |
| jsize cmdLen = (*env)->GetStringLength(env, cmd); | |
| WCHAR *pcmdCopy = (WCHAR*)malloc((cmdLen + 1) * sizeof(WCHAR)); | |
| if (pcmdCopy != NULL) { | |
| wmemcpy(pcmdCopy, pcmd, cmdLen); | |
| pcmdCopy[cmdLen] = 0; // null terminate | |
| ret = processCreate( | |
| env, | |
| pcmd, | |
| penvBlock, | |
| pdir, | |
| handles, | |
| redirectErrorStream); | |
| free(pcmdCopy); // free mutable command line | |
| } | |
| (*env)->ReleaseLongArrayElements(env, stdHandles, handles, 0); | |
| } | |
| if (pdir != NULL) | |
| (*env)->ReleaseStringChars(env, dir, pdir); | |
| } | |
| if (penvBlock != NULL) | |
| (*env)->ReleaseStringChars(env, envBlock, penvBlock); | |
| } | |
| (*env)->ReleaseStringChars(env, cmd, pcmd); | |
| } | |
| } | |
| return ret; | |
| } | |
| JNIEXPORT jint JNICALL | |
| Java_java_lang_ProcessImpl_getExitCodeProcess(JNIEnv *env, jclass ignored, jlong handle) | |
| { | |
| DWORD exit_code; | |
| if (GetExitCodeProcess((HANDLE) handle, &exit_code) == 0) | |
| win32Error(env, L"GetExitCodeProcess"); | |
| return exit_code; | |
| } | |
| JNIEXPORT jint JNICALL | |
| Java_java_lang_ProcessImpl_getStillActive(JNIEnv *env, jclass ignored) | |
| { | |
| return STILL_ACTIVE; | |
| } | |
| JNIEXPORT void JNICALL | |
| Java_java_lang_ProcessImpl_waitForInterruptibly(JNIEnv *env, jclass ignored, jlong handle) | |
| { | |
| HANDLE events[2]; | |
| events[0] = (HANDLE) handle; | |
| events[1] = JVM_GetThreadInterruptEvent(); | |
| if (WaitForMultipleObjects(sizeof(events)/sizeof(events[0]), events, | |
| FALSE, /* Wait for ANY event */ | |
| INFINITE) /* Wait forever */ | |
| == WAIT_FAILED) | |
| win32Error(env, L"WaitForMultipleObjects"); | |
| } | |
| JNIEXPORT void JNICALL | |
| Java_java_lang_ProcessImpl_waitForTimeoutInterruptibly(JNIEnv *env, | |
| jclass ignored, | |
| jlong handle, | |
| jlong timeoutMillis) | |
| { | |
| HANDLE events[2]; | |
| DWORD dwTimeout = (DWORD)timeoutMillis; | |
| DWORD result; | |
| events[0] = (HANDLE) handle; | |
| events[1] = JVM_GetThreadInterruptEvent(); | |
| result = WaitForMultipleObjects(sizeof(events)/sizeof(events[0]), events, | |
| FALSE, /* Wait for ANY event */ | |
| dwTimeout); /* Wait for dwTimeout */ | |
| if (result == WAIT_FAILED) | |
| win32Error(env, L"WaitForMultipleObjects"); | |
| } | |
| JNIEXPORT void JNICALL | |
| Java_java_lang_ProcessImpl_terminateProcess(JNIEnv *env, jclass ignored, jlong handle) | |
| { | |
| TerminateProcess((HANDLE) handle, 1); | |
| } | |
| JNIEXPORT jboolean JNICALL | |
| Java_java_lang_ProcessImpl_isProcessAlive(JNIEnv *env, jclass ignored, jlong handle) | |
| { | |
| return WaitForSingleObject((HANDLE) handle, 0) /* don't wait */ | |
| == WAIT_TIMEOUT; | |
| } | |
| JNIEXPORT jboolean JNICALL | |
| Java_java_lang_ProcessImpl_closeHandle(JNIEnv *env, jclass ignored, jlong handle) | |
| { | |
| return (jboolean) CloseHandle((HANDLE) handle); | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_java_lang_ProcessImpl_openForAtomicAppend(JNIEnv *env, jclass ignored, jstring path) | |
| { | |
| const DWORD access = (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA); | |
| const DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; | |
| const DWORD disposition = OPEN_ALWAYS; | |
| const DWORD flagsAndAttributes = FILE_ATTRIBUTE_NORMAL; | |
| HANDLE h; | |
| WCHAR *pathbuf = pathToNTPath(env, path, JNI_FALSE); | |
| if (pathbuf == NULL) { | |
| /* Exception already pending */ | |
| return -1; | |
| } | |
| h = CreateFileW( | |
| pathbuf, /* Wide char path name */ | |
| access, /* Read and/or write permission */ | |
| sharing, /* File sharing flags */ | |
| NULL, /* Security attributes */ | |
| disposition, /* creation disposition */ | |
| flagsAndAttributes, /* flags and attributes */ | |
| NULL); | |
| free(pathbuf); | |
| if (h == INVALID_HANDLE_VALUE) { | |
| JNU_ThrowIOExceptionWithLastError(env, "CreateFileW"); | |
| } | |
| return ptr_to_jlong(h); | |
| } |