Implemented ProcessLauncher on Windows.

This commit is contained in:
Adam Murdoch
2013-02-20 15:02:25 +11:00
parent af375d6114
commit 18c0d99835
11 changed files with 154 additions and 27 deletions

View File

@@ -59,6 +59,7 @@ task nativeHeaders {
args 'net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions'
args 'net.rubygrapefruit.platform.internal.jni.TerminfoFunctions'
args 'net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions'
args 'net.rubygrapefruit.platform.internal.jni.WindowsHandleFunctions'
}
}
}

View File

@@ -450,4 +450,31 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_clearToEnd
}
}
void uninheritStream(JNIEnv *env, DWORD stdInputHandle, jobject result) {
HANDLE streamHandle = GetStdHandle(stdInputHandle);
if (streamHandle == NULL) {
// We're not attached to a stdio (eg Desktop application). Ignore.
return;
}
if (streamHandle == INVALID_HANDLE_VALUE) {
mark_failed_with_errno(env, "could not get std handle", result);
return;
}
boolean ok = SetHandleInformation(streamHandle, HANDLE_FLAG_INHERIT, 0);
if (!ok) {
mark_failed_with_errno(env, "could not change std handle", result);
}
}
JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsHandleFunctions_markStandardHandlesUninheritable(JNIEnv *env, jclass target, jobject result) {
uninheritStream(env, STD_INPUT_HANDLE, result);
uninheritStream(env, STD_OUTPUT_HANDLE, result);
uninheritStream(env, STD_ERROR_HANDLE, result);
}
JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsHandleFunctions_restoreStandardHandles(JNIEnv *env, jclass target, jobject result) {
}
#endif

View File

@@ -23,7 +23,7 @@
extern "C" {
#endif
#define NATIVE_VERSION 14
#define NATIVE_VERSION 15
/*
* Marks the given result as failed, using the given error message

View File

@@ -19,19 +19,11 @@ package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.NativeException;
import net.rubygrapefruit.platform.ProcessLauncher;
import java.io.IOException;
public class DefaultProcessLauncher implements ProcessLauncher {
private final Object startLock = new Object();
public Process start(ProcessBuilder processBuilder) throws NativeException {
try {
synchronized (startLock) {
// Start a single process at a time, to avoid streams to child process being inherited by other
// children before the parent
return processBuilder.start();
}
} catch (IOException e) {
return processBuilder.start();
} catch (Exception e) {
throw new NativeException(String.format("Could not start '%s'", processBuilder.command().get(0)), e);
}
}

View File

@@ -91,11 +91,14 @@ public abstract class Platform {
@Override
public <T extends NativeIntegration> T get(Class<T> type, NativeLibraryLoader nativeLibraryLoader) {
if (type.equals(Process.class)) {
return type.cast(new WrapperProcess(new DefaultProcess()));
return type.cast(new WrapperProcess(new DefaultProcess(), true));
}
if (type.equals(Terminals.class)) {
return type.cast(new WindowsTerminals());
}
if (type.equals(ProcessLauncher.class)) {
return type.cast(new WrapperProcessLauncher(new WindowsProcessLauncher(new DefaultProcessLauncher())));
}
if (type.equals(SystemInfo.class)) {
return type.cast(new DefaultSystemInfo());
}
@@ -129,10 +132,10 @@ public abstract class Platform {
return type.cast(new DefaultPosixFile());
}
if (type.equals(Process.class)) {
return type.cast(new WrapperProcess(new DefaultProcess()));
return type.cast(new WrapperProcess(new DefaultProcess(), false));
}
if (type.equals(ProcessLauncher.class)) {
return type.cast(new DefaultProcessLauncher());
return type.cast(new WrapperProcessLauncher(new DefaultProcessLauncher()));
}
if (type.equals(Terminals.class)) {
nativeLibraryLoader.load(getCursesLibraryName());

View File

@@ -0,0 +1,30 @@
package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.NativeException;
import net.rubygrapefruit.platform.ProcessLauncher;
import net.rubygrapefruit.platform.internal.jni.WindowsHandleFunctions;
public class WindowsProcessLauncher implements ProcessLauncher {
private final ProcessLauncher launcher;
public WindowsProcessLauncher(ProcessLauncher launcher) {
this.launcher = launcher;
}
public Process start(ProcessBuilder processBuilder) throws NativeException {
FunctionResult result = new FunctionResult();
WindowsHandleFunctions.markStandardHandlesUninheritable(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not start '%s': %s", processBuilder.command().get(0),
result.getMessage()));
}
try {
return launcher.start(processBuilder);
} finally {
WindowsHandleFunctions.restoreStandardHandles(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not restore process handles: %s", result.getMessage()));
}
}
}
}

View File

@@ -31,11 +31,15 @@ import java.util.Map;
@ThreadSafe
public class WrapperProcess implements Process {
private final Process process;
private final boolean windows;
private final Object workingDirectoryLock = new Object();
private final Object environmentLock = new Object();
private Map<String, String> environment;
private Map<String, String> windowsEnvironment;
public WrapperProcess(Process process) {
public WrapperProcess(Process process, boolean windows) {
this.process = process;
this.windows = windows;
}
@Override
@@ -81,22 +85,44 @@ public class WrapperProcess implements Process {
private void removeEnvInternal(String name) {
getEnv().remove(name);
if (windows) {
getWindowsEnv().remove(name);
}
}
private void setEnvInternal(String name, String value) {
getEnv().put(name, value);
}
private Map<String, String> getEnv() {
try {
Map<String, String> theUnmodifiableEnvironment = System.getenv();
Class<?> cu = theUnmodifiableEnvironment.getClass();
Field m = cu.getDeclaredField("m");
m.setAccessible(true);
return (Map<String, String>)m.get(theUnmodifiableEnvironment);
} catch (Exception e) {
throw new NativeException("Unable to get mutable environment map.", e);
if (windows) {
getWindowsEnv().put(name, value);
}
}
private Map<String, String> getEnv() {
if (environment == null) {
try {
Map<String, String> theUnmodifiableEnvironment = System.getenv();
Class<?> cu = theUnmodifiableEnvironment.getClass();
Field m = cu.getDeclaredField("m");
m.setAccessible(true);
environment = (Map<String, String>) m.get(theUnmodifiableEnvironment);
} catch (Exception e) {
throw new NativeException("Unable to get mutable environment variable map.", e);
}
}
return environment;
}
private Map<String, String> getWindowsEnv() {
if (windowsEnvironment == null) {
try {
Class<?> sc = Class.forName("java.lang.ProcessEnvironment");
Field caseinsensitive = sc.getDeclaredField("theCaseInsensitiveEnvironment");
caseinsensitive.setAccessible(true);
windowsEnvironment = (Map<String, String>) caseinsensitive.get(null);
} catch (Exception e) {
throw new NativeException("Unable to get mutable Windows environment variable map", e);
}
}
return windowsEnvironment;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2012 Adam Murdoch
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.NativeException;
import net.rubygrapefruit.platform.ProcessLauncher;
import net.rubygrapefruit.platform.ThreadSafe;
@ThreadSafe
public class WrapperProcessLauncher implements ProcessLauncher {
private final Object startLock = new Object();
private final ProcessLauncher launcher;
public WrapperProcessLauncher(ProcessLauncher launcher) {
this.launcher = launcher;
}
public Process start(ProcessBuilder processBuilder) throws NativeException {
synchronized (startLock) {
// Start a single process at a time, to avoid streams to child process being inherited by other
// children before the parent can close them
return launcher.start(processBuilder);
}
}
}

View File

@@ -20,7 +20,7 @@ import net.rubygrapefruit.platform.internal.FunctionResult;
import net.rubygrapefruit.platform.internal.MutableSystemInfo;
public class NativeLibraryFunctions {
public static final int VERSION = 14;
public static final int VERSION = 15;
public static native int getVersion();

View File

@@ -0,0 +1,9 @@
package net.rubygrapefruit.platform.internal.jni;
import net.rubygrapefruit.platform.internal.FunctionResult;
public class WindowsHandleFunctions {
public static native void markStandardHandlesUninheritable(FunctionResult result);
public static native void restoreStandardHandles(FunctionResult result);
}

View File