From 18c0d998357ebb706610df61e6adcc12dc5e010d Mon Sep 17 00:00:00 2001 From: Adam Murdoch Date: Wed, 20 Feb 2013 15:02:25 +1100 Subject: [PATCH] Implemented ProcessLauncher on Windows. --- build.gradle | 1 + src/main/cpp/win.cpp | 27 ++++++++++ src/main/headers/generic.h | 2 +- .../internal/DefaultProcessLauncher.java | 12 +---- .../platform/internal/Platform.java | 9 ++-- .../internal/WindowsProcessLauncher.java | 30 +++++++++++ .../platform/internal/WrapperProcess.java | 50 ++++++++++++++----- .../internal/WrapperProcessLauncher.java | 39 +++++++++++++++ .../internal/jni/NativeLibraryFunctions.java | 2 +- .../internal/jni/WindowsHandleFunctions.java | 9 ++++ .../platform/ProcessLauncherTest.groovy | 0 11 files changed, 154 insertions(+), 27 deletions(-) mode change 100644 => 100755 src/main/java/net/rubygrapefruit/platform/internal/DefaultProcessLauncher.java create mode 100755 src/main/java/net/rubygrapefruit/platform/internal/WindowsProcessLauncher.java mode change 100644 => 100755 src/main/java/net/rubygrapefruit/platform/internal/WrapperProcess.java create mode 100755 src/main/java/net/rubygrapefruit/platform/internal/WrapperProcessLauncher.java create mode 100755 src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsHandleFunctions.java mode change 100644 => 100755 src/test/groovy/net/rubygrapefruit/platform/ProcessLauncherTest.groovy diff --git a/build.gradle b/build.gradle index c151931..97aa77d 100755 --- a/build.gradle +++ b/build.gradle @@ -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' } } } diff --git a/src/main/cpp/win.cpp b/src/main/cpp/win.cpp index 9801b99..586c62f 100755 --- a/src/main/cpp/win.cpp +++ b/src/main/cpp/win.cpp @@ -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 diff --git a/src/main/headers/generic.h b/src/main/headers/generic.h index f9a621c..35b50af 100755 --- a/src/main/headers/generic.h +++ b/src/main/headers/generic.h @@ -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 diff --git a/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcessLauncher.java b/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcessLauncher.java old mode 100644 new mode 100755 index 261f7bf..e4e1c6a --- a/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcessLauncher.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcessLauncher.java @@ -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); } } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java index 6787103..0956961 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java @@ -91,11 +91,14 @@ public abstract class Platform { @Override public T get(Class 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()); diff --git a/src/main/java/net/rubygrapefruit/platform/internal/WindowsProcessLauncher.java b/src/main/java/net/rubygrapefruit/platform/internal/WindowsProcessLauncher.java new file mode 100755 index 0000000..10c73d2 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/WindowsProcessLauncher.java @@ -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())); + } + } + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/WrapperProcess.java b/src/main/java/net/rubygrapefruit/platform/internal/WrapperProcess.java old mode 100644 new mode 100755 index f57b9df..f9582f2 --- a/src/main/java/net/rubygrapefruit/platform/internal/WrapperProcess.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/WrapperProcess.java @@ -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 environment; + private Map 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 getEnv() { - try { - Map theUnmodifiableEnvironment = System.getenv(); - Class cu = theUnmodifiableEnvironment.getClass(); - Field m = cu.getDeclaredField("m"); - m.setAccessible(true); - return (Map)m.get(theUnmodifiableEnvironment); - } catch (Exception e) { - throw new NativeException("Unable to get mutable environment map.", e); + if (windows) { + getWindowsEnv().put(name, value); } } + private Map getEnv() { + if (environment == null) { + try { + Map theUnmodifiableEnvironment = System.getenv(); + Class cu = theUnmodifiableEnvironment.getClass(); + Field m = cu.getDeclaredField("m"); + m.setAccessible(true); + environment = (Map) m.get(theUnmodifiableEnvironment); + } catch (Exception e) { + throw new NativeException("Unable to get mutable environment variable map.", e); + } + } + return environment; + } + + private Map getWindowsEnv() { + if (windowsEnvironment == null) { + try { + Class sc = Class.forName("java.lang.ProcessEnvironment"); + Field caseinsensitive = sc.getDeclaredField("theCaseInsensitiveEnvironment"); + caseinsensitive.setAccessible(true); + windowsEnvironment = (Map) caseinsensitive.get(null); + } catch (Exception e) { + throw new NativeException("Unable to get mutable Windows environment variable map", e); + } + } + return windowsEnvironment; + } } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/WrapperProcessLauncher.java b/src/main/java/net/rubygrapefruit/platform/internal/WrapperProcessLauncher.java new file mode 100755 index 0000000..a7aa01b --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/WrapperProcessLauncher.java @@ -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); + } + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java index c35ab0e..1a3576d 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java @@ -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(); diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsHandleFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsHandleFunctions.java new file mode 100755 index 0000000..1fbe7d5 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsHandleFunctions.java @@ -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); +} diff --git a/src/test/groovy/net/rubygrapefruit/platform/ProcessLauncherTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/ProcessLauncherTest.groovy old mode 100644 new mode 100755