Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

run sendctrlc.exe with Java (no console window, simpler code) (#60, #55) #61

Merged
merged 1 commit into from
Dec 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions native/java-interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ JNIEXPORT jboolean JNICALL Java_org_jvnet_winp_Native_kill(JNIEnv* env, jclass c
return KillProcessEx(pid, recursive);
}

JNIEXPORT jboolean JNICALL Java_org_jvnet_winp_Native_sendCtrlC(JNIEnv* env, jclass clazz, jint pid, jstring sendctrlcExePath) {
const wchar_t* exePath = (wchar_t*)env->GetStringChars(sendctrlcExePath, NULL);
return SendCtrlC(env, clazz, pid, exePath);
}

JNIEXPORT jint JNICALL Java_org_jvnet_winp_Native_setPriority(JNIEnv* env, jclass clazz, jint pid, jint priority) {
auto_handle hProcess = OpenProcess(PROCESS_SET_INFORMATION, FALSE, pid);
if(hProcess && SetPriorityClass(hProcess, priority)) {
Expand Down
8 changes: 0 additions & 8 deletions native/java-interface.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 0 additions & 57 deletions native/winp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,63 +13,6 @@
// This file uses a long buffer, because it prints executable paths in some cases.
#define ERRMSG_SIZE 512

//---------------------------------------------------------------------------
// SendCtrlC
//
// Sends CTRL+C to the specified process.
//
// Parameters:
// dwProcessId - identifier of the process to terminate
//
// Returns:
// TRUE, if successful, FALSE - otherwise.
// When used from JNI, exceptions may be thrown instead
//
BOOL WINAPI SendCtrlC(JNIEnv* pEnv, jclass clazz, IN DWORD dwProcessId, const wchar_t* pExePath) {
char errorBuffer[ERRMSG_SIZE];
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));

std::wstring exepath(pExePath);
std::wstring cmd = L'"' + exepath + L"\" " + std::to_wstring(dwProcessId);
std::vector<wchar_t> cmd_buffer(cmd.begin(), cmd.end()); // with C++17, could just use cmd.data()

BOOL started = CreateProcessW(NULL, &cmd_buffer[0], NULL, NULL,
FALSE, 0, NULL, NULL, &si, &pi);

BOOL success = FALSE;
if (started) {
// wait for termination if the process started, max. 5 secs
DWORD ret = WaitForSingleObject(pi.hProcess, 5000);
if (ret != WAIT_OBJECT_0) {
sprintf_s<ERRMSG_SIZE>(errorBuffer, "Failed to send Ctrl+C to process with pid=%d. WaitForSingleObject exit code: %d (last error: d).", dwProcessId, ret, GetLastError());
reportError(pEnv, errorBuffer);
}

// then set success flag if the exit code was 0
DWORD exit_code;
if (GetExitCodeProcess(pi.hProcess, &exit_code) != FALSE) {
success = (exit_code == 0);
if (exit_code != 0) {
sprintf_s<ERRMSG_SIZE>(errorBuffer, "External Ctrl+C execution failed for process pid=%d. Ctrl+C process exited with code %d: %s.", dwProcessId, exit_code,
(exit_code == -1) ? "Wrong arguments" : "Failed to attach to the console (see the AttachConsole WinAPI call)");
reportError(pEnv, errorBuffer);
}
}
} else {
sprintf_s<ERRMSG_SIZE>(errorBuffer, "Failed to send Ctrl+C to process with pid=%d. Signal process did not start: %s.", dwProcessId, cmd);
reportError(pEnv, errorBuffer);
}

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

return success;
}

//---------------------------------------------------------------------------
// KillProcess
//
Expand Down
2 changes: 0 additions & 2 deletions native/winp.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
#define reportError(env,msg) error(env,__FILE__,__LINE__,msg);
void error(JNIEnv* env, const char* file, int line, const char* msg);

BOOL WINAPI SendCtrlC(JNIEnv* pEnv, jclass clazz, IN DWORD dwProcessId, const wchar_t* pExePath);

//
// Kernel32.dll
//
Expand Down
106 changes: 106 additions & 0 deletions src/main/java/org/jvnet/winp/CtrlCSender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.jvnet.winp;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.Locale;

class CtrlCSender {

private static final int TIMEOUT_MILLIS = 5000;

static boolean sendCtrlC(int pid, String ctrlCExePath) {
ProcessBuilder builder = new ProcessBuilder(ctrlCExePath, String.valueOf(pid));
builder.redirectErrorStream(true);
Process process;
try {
process = builder.start();
} catch (IOException e) {
throw new WinpException(e);
}
StreamGobbler stdout = new StreamGobbler(new InputStreamReader(process.getInputStream(), Charset.defaultCharset()));
Integer exitCode = null;
try {
exitCode = waitFor(process);
} catch (InterruptedException ignored) {
}
stdout.stop();
if (exitCode == null) {
process.destroy();
throw new WinpException("Failed to send Ctrl+C to " + pid + ": " + TIMEOUT_MILLIS + " ms timeout exceeded");
}
if (exitCode == 0) {
return true;
}
throw new WinpException("Failed to send Ctrl+C, " + new File(ctrlCExePath).getName() +
" terminated with exit code " + stringifyExitCode(exitCode) + ", output: " + stdout.getText());
}

private static String stringifyExitCode(int exitCode) {
if (exitCode >= 0xC0000000 && exitCode < 0xD0000000) {
// http://support.microsoft.com/kb/308558:
// If the result code has the "C0000XXX" format, the task did not complete successfully (the "C" indicates an error condition).
// The most common "C" error code is "0xC000013A: The application terminated as a result of a CTRL+C".
return exitCode + " (0x" + Integer.toHexString(exitCode).toUpperCase(Locale.ENGLISH) + ")";
}
return String.valueOf(exitCode);
}

private static Integer waitFor(Process process) throws InterruptedException {
long endTime = System.currentTimeMillis() + TIMEOUT_MILLIS;
int i = 0;
do {
try {
return process.exitValue();
}
catch (IllegalThreadStateException ignore) {
Thread.sleep(i++ < 3 ? 10 : i < 5 ? 30 : 100);
}
} while (System.currentTimeMillis() < endTime);
return null;
}

private static class StreamGobbler implements Runnable {

private final Reader reader;
private final StringBuilder myBuffer = new StringBuilder();
private final Thread thread;
private boolean isStopped = false;

private StreamGobbler(Reader reader) {
this.reader = reader;
this.thread = new Thread(this, "sendctrlc.exe output reader");
this.thread.start();
}

public void run() {
char[] buf = new char[8192];
try {
int readCount;
while (!isStopped && (readCount = reader.read(buf)) >= 0) {
myBuffer.append(buf, 0, readCount);
}
if (isStopped) {
myBuffer.append("Failed to read output: force stopped");
}
}
catch (Exception e) {
myBuffer.append("Failed to read output: ").append(e.getClass().getName()).append(" raised");
}
}

private void stop() {
try {
this.thread.join(1000); // await to read whole buffered output
} catch (InterruptedException ignored) {
}
this.isStopped = true;
}

private String getText() {
return myBuffer.toString();
}
}
}
5 changes: 2 additions & 3 deletions src/main/java/org/jvnet/winp/Native.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class Native {
public static final String CTRLCEXE_NAME = "64".equals(System.getProperty("sun.arch.data.model")) ? "sendctrlc.x64" : "sendctrlc";

native static boolean kill(int pid, boolean recursive);
native static boolean sendCtrlC(int pid, String sendctrlcExePath);
native static boolean isCriticalProcess(int pid);
native static boolean isProcessRunning(int pid);
native static int setPriority(int pid, int value);
Expand Down Expand Up @@ -75,7 +74,7 @@ class Native {

/**
* Sends Ctrl+C to the process.
* Due to the Windows platform specifics, this execution will spawn a separate thread to deliver the signal.
* Due to the Windows platform specifics, this execution will spawn a separate process to deliver the signal.
* This process is expected to be executed within a 5-second timeout.
* @param pid PID to receive the signal
* @return {@code true} if the signal was delivered successfully
Expand All @@ -87,7 +86,7 @@ public static boolean sendCtrlC(int pid) throws WinpException {
LOGGER.log(Level.WARNING, "Cannot send the CtrlC signal to the process. Cannot find the executable {0}.dll", CTRLCEXE_NAME);
return false;
}
return sendCtrlC(pid, ctrlCExePath);
return CtrlCSender.sendCtrlC(pid, ctrlCExePath);
}

static {
Expand Down