diff --git a/openjdk.props b/openjdk.props index 81a4929efb..5d606918eb 100644 --- a/openjdk.props +++ b/openjdk.props @@ -796,6 +796,7 @@ + diff --git a/src/IKVM.Java/local/java/io/FileDescriptor.java b/src/IKVM.Java/local/java/io/FileDescriptor.java index 76a6bd2b54..50d4db9b7a 100644 --- a/src/IKVM.Java/local/java/io/FileDescriptor.java +++ b/src/IKVM.Java/local/java/io/FileDescriptor.java @@ -22,7 +22,7 @@ public final class FileDescriptor { sun.misc.SharedSecrets.setJavaIOFileDescriptorAccess( new sun.misc.JavaIOFileDescriptorAccess() { public void set(FileDescriptor obj, int fd) { - + obj.fd = fd; } public int get(FileDescriptor obj) { @@ -30,7 +30,7 @@ public int get(FileDescriptor obj) { } public void setHandle(FileDescriptor obj, long handle) { - + obj.handle = handle; } public long getHandle(FileDescriptor obj) { diff --git a/src/IKVM.Java/local/java/lang/ProcessImpl.java b/src/IKVM.Java/local/java/lang/ProcessImpl.java index ae211b1e71..a931aa029d 100644 --- a/src/IKVM.Java/local/java/lang/ProcessImpl.java +++ b/src/IKVM.Java/local/java/lang/ProcessImpl.java @@ -1,46 +1,156 @@ +/* + * Copyright (c) 2003, 2010, 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. + */ + package java.lang; -import java.io.*; +import java.io.IOException; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.lang.ProcessBuilder.Redirect; import java.util.*; -final class ProcessImpl extends Process { +/** + * This class is for the exclusive use of ProcessBuilder.start() to + * create new processes. + * + * @author Martin Buchholz + * @since 1.5 + */ +final class ProcessImpl { + + public native void dummy(); - cli.System.Diagnostics.Process process; - OutputStream outputStream; - InputStream inputStream; - InputStream errorStream; + private static final sun.misc.JavaIOFileDescriptorAccess fdAccess + = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); - static native Process start(String cmdarray[], java.util.Map environment, String dir, ProcessBuilder.Redirect[] redirects, boolean redirectErrorStream) throws IOException; + private ProcessImpl() {} // Not instantiable - private ProcessImpl(cli.System.Diagnostics.Process process, OutputStream outputStream, InputStream inputStream, InputStream errorStream) { - this.process = process; - this.outputStream = outputStream; - this.inputStream = inputStream; - this.errorStream = errorStream; + private static byte[] toCString(String s) { + if (s == null) + return null; + byte[] bytes = s.getBytes(); + byte[] result = new byte[bytes.length + 1]; + System.arraycopy(bytes, 0, + result, 0, + bytes.length); + result[result.length-1] = (byte)0; + return result; } - @Override - public OutputStream getOutputStream() { - return outputStream; - } + // Only for use by ProcessBuilder.start() + static Process start(String[] cmdarray, + java.util.Map environment, + String dir, + ProcessBuilder.Redirect[] redirects, + boolean redirectErrorStream) + throws IOException + { + if (cli.IKVM.Runtime.RuntimeUtil.get_IsWindows()) { + return Win32Process.start(cmdarray, environment, dir, redirects, redirectErrorStream); + } else { - @Override - public InputStream getInputStream() { - return inputStream; - } - - @Override - public InputStream getErrorStream() { - return errorStream; - } - - @Override - public native int waitFor() throws InterruptedException; - - @Override - public native int exitValue(); - - @Override - public native void destroy(); + assert cmdarray != null && cmdarray.length > 0; + + // Convert arguments to a contiguous block; it's easier to do + // memory management in Java than in C. + byte[][] args = new byte[cmdarray.length-1][]; + int size = args.length; // For added NUL bytes + for (int i = 0; i < args.length; i++) { + args[i] = cmdarray[i+1].getBytes(); + size += args[i].length; + } + byte[] argBlock = new byte[size]; + int i = 0; + for (byte[] arg : args) { + System.arraycopy(arg, 0, argBlock, i, arg.length); + i += arg.length + 1; + // No need to write NUL bytes explicitly + } + int[] envc = new int[1]; + byte[] envBlock = UNIXProcessEnvironment.toEnvironmentBlock(environment, envc); + + int[] std_fds; + + FileInputStream f0 = null; + FileOutputStream f1 = null; + FileOutputStream f2 = null; + + try { + if (redirects == null) { + std_fds = new int[] { -1, -1, -1 }; + } else { + std_fds = new int[3]; + + if (redirects[0] == Redirect.PIPE) + std_fds[0] = -1; + else if (redirects[0] == Redirect.INHERIT) + std_fds[0] = 0; + else { + f0 = new FileInputStream(redirects[0].file()); + std_fds[0] = fdAccess.get(f0.getFD()); + } + + if (redirects[1] == Redirect.PIPE) + std_fds[1] = -1; + else if (redirects[1] == Redirect.INHERIT) + std_fds[1] = 1; + else { + f1 = new FileOutputStream(redirects[1].file(), + redirects[1].append()); + std_fds[1] = fdAccess.get(f1.getFD()); + } + + if (redirects[2] == Redirect.PIPE) + std_fds[2] = -1; + else if (redirects[2] == Redirect.INHERIT) + std_fds[2] = 2; + else { + f2 = new FileOutputStream(redirects[2].file(), + redirects[2].append()); + std_fds[2] = fdAccess.get(f2.getFD()); + } + } + + return new UNIXProcess + (toCString(cmdarray[0]), + argBlock, args.length, + envBlock, envc[0], + toCString(dir), + std_fds, + redirectErrorStream); + } finally { + // In theory, close() can throw IOException + // (although it is rather unlikely to happen here) + try { if (f0 != null) f0.close(); } + finally { + try { if (f1 != null) f1.close(); } + finally { if (f2 != null) f2.close(); } + } + } + + } + } } diff --git a/src/IKVM.Java/local/java/lang/UNIXProcessEnvironment.java b/src/IKVM.Java/local/java/lang/UNIXProcessEnvironment.java new file mode 100644 index 0000000000..e51dcaab90 --- /dev/null +++ b/src/IKVM.Java/local/java/lang/UNIXProcessEnvironment.java @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2003, 2011, 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. + */ + +/* We use APIs that access the standard Unix environ array, which + * is defined by UNIX98 to look like: + * + * char **environ; + * + * These are unsorted, case-sensitive, null-terminated arrays of bytes + * of the form FOO=BAR\000 which are usually encoded in the user's + * default encoding (file.encoding is an excellent choice for + * encoding/decoding these). However, even though the user cannot + * directly access the underlying byte representation, we take pains + * to pass on the child the exact byte representation we inherit from + * the parent process for any environment name or value not created by + * Javaland. So we keep track of all the byte representations. + * + * Internally, we define the types Variable and Value that exhibit + * String/byteArray duality. The internal representation of the + * environment then looks like a Map. But we don't + * expose this to the user -- we only provide a Map + * view, although we could also provide a Map view. + * + * The non-private methods in this class are not for general use even + * within this package. Instead, they are the system-dependent parts + * of the system-independent method of the same name. Don't even + * think of using this class unless your method's name appears below. + * + * @author Martin Buchholz + * @since 1.5 + */ + +package java.lang; + +import java.io.*; +import java.util.*; + + +final class UNIXProcessEnvironment +{ + private static final HashMap theEnvironment; + private static final Map theUnmodifiableEnvironment; + static final int MIN_NAME_LENGTH = 0; + + static { + // We cache the C environment. This means that subsequent calls + // to putenv/setenv from C will not be visible from Java code. + byte[][] environ = environ(); + theEnvironment = new HashMap<>(environ.length/2 + 3); + // Read environment variables back to front, + // so that earlier variables override later ones. + for (int i = environ.length-1; i > 0; i-=2) + theEnvironment.put(Variable.valueOf(environ[i-1]), + Value.valueOf(environ[i])); + + theUnmodifiableEnvironment + = Collections.unmodifiableMap + (new StringEnvironment(theEnvironment)); + } + + /* Only for use by System.getenv(String) */ + static String getenv(String name) { + return theUnmodifiableEnvironment.get(name); + } + + /* Only for use by System.getenv() */ + static Map getenv() { + return theUnmodifiableEnvironment; + } + + /* Only for use by ProcessBuilder.environment() */ + @SuppressWarnings("unchecked") + static Map environment() { + return new StringEnvironment + ((Map)(theEnvironment.clone())); + } + + /* Only for use by Runtime.exec(...String[]envp...) */ + static Map emptyEnvironment(int capacity) { + return new StringEnvironment(new HashMap(capacity)); + } + + private static native byte[][] environ(); + + // This class is not instantiable. + private UNIXProcessEnvironment() {} + + // Check that name is suitable for insertion into Environment map + private static void validateVariable(String name) { + if (name.indexOf('=') != -1 || + name.indexOf('\u0000') != -1) + throw new IllegalArgumentException + ("Invalid environment variable name: \"" + name + "\""); + } + + // Check that value is suitable for insertion into Environment map + private static void validateValue(String value) { + if (value.indexOf('\u0000') != -1) + throw new IllegalArgumentException + ("Invalid environment variable value: \"" + value + "\""); + } + + // A class hiding the byteArray-String duality of + // text data on Unixoid operating systems. + private static abstract class ExternalData { + protected final String str; + protected final byte[] bytes; + + protected ExternalData(String str, byte[] bytes) { + this.str = str; + this.bytes = bytes; + } + + public byte[] getBytes() { + return bytes; + } + + public String toString() { + return str; + } + + public boolean equals(Object o) { + return o instanceof ExternalData + && arrayEquals(getBytes(), ((ExternalData) o).getBytes()); + } + + public int hashCode() { + return arrayHash(getBytes()); + } + } + + private static class Variable + extends ExternalData implements Comparable + { + protected Variable(String str, byte[] bytes) { + super(str, bytes); + } + + public static Variable valueOfQueryOnly(Object str) { + return valueOfQueryOnly((String) str); + } + + public static Variable valueOfQueryOnly(String str) { + return new Variable(str, str.getBytes()); + } + + public static Variable valueOf(String str) { + validateVariable(str); + return valueOfQueryOnly(str); + } + + public static Variable valueOf(byte[] bytes) { + return new Variable(new String(bytes), bytes); + } + + public int compareTo(Variable variable) { + return arrayCompare(getBytes(), variable.getBytes()); + } + + public boolean equals(Object o) { + return o instanceof Variable && super.equals(o); + } + } + + private static class Value + extends ExternalData implements Comparable + { + protected Value(String str, byte[] bytes) { + super(str, bytes); + } + + public static Value valueOfQueryOnly(Object str) { + return valueOfQueryOnly((String) str); + } + + public static Value valueOfQueryOnly(String str) { + return new Value(str, str.getBytes()); + } + + public static Value valueOf(String str) { + validateValue(str); + return valueOfQueryOnly(str); + } + + public static Value valueOf(byte[] bytes) { + return new Value(new String(bytes), bytes); + } + + public int compareTo(Value value) { + return arrayCompare(getBytes(), value.getBytes()); + } + + public boolean equals(Object o) { + return o instanceof Value && super.equals(o); + } + } + + // This implements the String map view the user sees. + private static class StringEnvironment + extends AbstractMap + { + private Map m; + private static String toString(Value v) { + return v == null ? null : v.toString(); + } + public StringEnvironment(Map m) {this.m = m;} + public int size() {return m.size();} + public boolean isEmpty() {return m.isEmpty();} + public void clear() { m.clear();} + public boolean containsKey(Object key) { + return m.containsKey(Variable.valueOfQueryOnly(key)); + } + public boolean containsValue(Object value) { + return m.containsValue(Value.valueOfQueryOnly(value)); + } + public String get(Object key) { + return toString(m.get(Variable.valueOfQueryOnly(key))); + } + public String put(String key, String value) { + return toString(m.put(Variable.valueOf(key), + Value.valueOf(value))); + } + public String remove(Object key) { + return toString(m.remove(Variable.valueOfQueryOnly(key))); + } + public Set keySet() { + return new StringKeySet(m.keySet()); + } + public Set> entrySet() { + return new StringEntrySet(m.entrySet()); + } + public Collection values() { + return new StringValues(m.values()); + } + + // It is technically feasible to provide a byte-oriented view + // as follows: + // public Map asByteArrayMap() { + // return new ByteArrayEnvironment(m); + // } + + + // Convert to Unix style environ as a monolithic byte array + // inspired by the Windows Environment Block, except we work + // exclusively with bytes instead of chars, and we need only + // one trailing NUL on Unix. + // This keeps the JNI as simple and efficient as possible. + public byte[] toEnvironmentBlock(int[]envc) { + int count = m.size() * 2; // For added '=' and NUL + for (Map.Entry entry : m.entrySet()) { + count += entry.getKey().getBytes().length; + count += entry.getValue().getBytes().length; + } + + byte[] block = new byte[count]; + + int i = 0; + for (Map.Entry entry : m.entrySet()) { + byte[] key = entry.getKey ().getBytes(); + byte[] value = entry.getValue().getBytes(); + System.arraycopy(key, 0, block, i, key.length); + i+=key.length; + block[i++] = (byte) '='; + System.arraycopy(value, 0, block, i, value.length); + i+=value.length + 1; + // No need to write NUL byte explicitly + //block[i++] = (byte) '\u0000'; + } + envc[0] = m.size(); + return block; + } + } + + static byte[] toEnvironmentBlock(Map map, int[]envc) { + return map == null ? null : + ((StringEnvironment)map).toEnvironmentBlock(envc); + } + + + private static class StringEntry + implements Map.Entry + { + private final Map.Entry e; + public StringEntry(Map.Entry e) {this.e = e;} + public String getKey() {return e.getKey().toString();} + public String getValue() {return e.getValue().toString();} + public String setValue(String newValue) { + return e.setValue(Value.valueOf(newValue)).toString(); + } + public String toString() {return getKey() + "=" + getValue();} + public boolean equals(Object o) { + return o instanceof StringEntry + && e.equals(((StringEntry)o).e); + } + public int hashCode() {return e.hashCode();} + } + + private static class StringEntrySet + extends AbstractSet> + { + private final Set> s; + public StringEntrySet(Set> s) {this.s = s;} + public int size() {return s.size();} + public boolean isEmpty() {return s.isEmpty();} + public void clear() { s.clear();} + public Iterator> iterator() { + return new Iterator>() { + Iterator> i = s.iterator(); + public boolean hasNext() {return i.hasNext();} + public Map.Entry next() { + return new StringEntry(i.next()); + } + public void remove() {i.remove();} + }; + } + private static Map.Entry vvEntry(final Object o) { + if (o instanceof StringEntry) + return ((StringEntry)o).e; + return new Map.Entry() { + public Variable getKey() { + return Variable.valueOfQueryOnly(((Map.Entry)o).getKey()); + } + public Value getValue() { + return Value.valueOfQueryOnly(((Map.Entry)o).getValue()); + } + public Value setValue(Value value) { + throw new UnsupportedOperationException(); + } + }; + } + public boolean contains(Object o) { return s.contains(vvEntry(o)); } + public boolean remove(Object o) { return s.remove(vvEntry(o)); } + public boolean equals(Object o) { + return o instanceof StringEntrySet + && s.equals(((StringEntrySet) o).s); + } + public int hashCode() {return s.hashCode();} + } + + private static class StringValues + extends AbstractCollection + { + private final Collection c; + public StringValues(Collection c) {this.c = c;} + public int size() {return c.size();} + public boolean isEmpty() {return c.isEmpty();} + public void clear() { c.clear();} + public Iterator iterator() { + return new Iterator() { + Iterator i = c.iterator(); + public boolean hasNext() {return i.hasNext();} + public String next() {return i.next().toString();} + public void remove() {i.remove();} + }; + } + public boolean contains(Object o) { + return c.contains(Value.valueOfQueryOnly(o)); + } + public boolean remove(Object o) { + return c.remove(Value.valueOfQueryOnly(o)); + } + public boolean equals(Object o) { + return o instanceof StringValues + && c.equals(((StringValues)o).c); + } + public int hashCode() {return c.hashCode();} + } + + private static class StringKeySet extends AbstractSet { + private final Set s; + public StringKeySet(Set s) {this.s = s;} + public int size() {return s.size();} + public boolean isEmpty() {return s.isEmpty();} + public void clear() { s.clear();} + public Iterator iterator() { + return new Iterator() { + Iterator i = s.iterator(); + public boolean hasNext() {return i.hasNext();} + public String next() {return i.next().toString();} + public void remove() { i.remove();} + }; + } + public boolean contains(Object o) { + return s.contains(Variable.valueOfQueryOnly(o)); + } + public boolean remove(Object o) { + return s.remove(Variable.valueOfQueryOnly(o)); + } + } + + // Replace with general purpose method someday + private static int arrayCompare(byte[]x, byte[] y) { + int min = x.length < y.length ? x.length : y.length; + for (int i = 0; i < min; i++) + if (x[i] != y[i]) + return x[i] - y[i]; + return x.length - y.length; + } + + // Replace with general purpose method someday + private static boolean arrayEquals(byte[] x, byte[] y) { + if (x.length != y.length) + return false; + for (int i = 0; i < x.length; i++) + if (x[i] != y[i]) + return false; + return true; + } + + // Replace with general purpose method someday + private static int arrayHash(byte[] x) { + int hash = 0; + for (int i = 0; i < x.length; i++) + hash = 31 * hash + x[i]; + return hash; + } + +} diff --git a/src/IKVM.Java/local/java/lang/Win32Process.java b/src/IKVM.Java/local/java/lang/Win32Process.java new file mode 100644 index 0000000000..9f804716be --- /dev/null +++ b/src/IKVM.Java/local/java/lang/Win32Process.java @@ -0,0 +1,46 @@ +package java.lang; + +import java.io.*; +import java.util.*; + +final class Win32Process extends Process { + + cli.System.Diagnostics.Process process; + OutputStream outputStream; + InputStream inputStream; + InputStream errorStream; + + static native Process start(String cmdarray[], java.util.Map environment, String dir, ProcessBuilder.Redirect[] redirects, boolean redirectErrorStream) throws IOException; + + private Win32Process(cli.System.Diagnostics.Process process, OutputStream outputStream, InputStream inputStream, InputStream errorStream) { + this.process = process; + this.outputStream = outputStream; + this.inputStream = inputStream; + this.errorStream = errorStream; + } + + @Override + public OutputStream getOutputStream() { + return outputStream; + } + + @Override + public InputStream getInputStream() { + return inputStream; + } + + @Override + public InputStream getErrorStream() { + return errorStream; + } + + @Override + public native int waitFor() throws InterruptedException; + + @Override + public native int exitValue(); + + @Override + public native void destroy(); + +} diff --git a/src/IKVM.Runtime/Accessors/Java/Lang/ProcessImplAccessor.cs b/src/IKVM.Runtime/Accessors/Java/Lang/Win32ProcessAccessor.cs similarity index 77% rename from src/IKVM.Runtime/Accessors/Java/Lang/ProcessImplAccessor.cs rename to src/IKVM.Runtime/Accessors/Java/Lang/Win32ProcessAccessor.cs index df6f9aa7ea..8ad55e96c2 100644 --- a/src/IKVM.Runtime/Accessors/Java/Lang/ProcessImplAccessor.cs +++ b/src/IKVM.Runtime/Accessors/Java/Lang/Win32ProcessAccessor.cs @@ -7,9 +7,9 @@ namespace IKVM.Runtime.Accessors.Java.Lang #if FIRST_PASS == false && EXPORTER == false && IMPORTER == false /// - /// Provides runtime access to the 'java.io.ProcessImpl' type. + /// Provides runtime access to the 'java.lang.Win32Process' type. /// - internal sealed class ProcessImplAccessor : Accessor + internal sealed class Win32ProcessAccessor : Accessor { MethodAccessor> init; @@ -19,8 +19,8 @@ internal sealed class ProcessImplAccessor : Accessor /// Initializes a new instance. /// /// - public ProcessImplAccessor(AccessorTypeResolver resolver) : - base(resolver, "java.lang.ProcessImpl") + public Win32ProcessAccessor(AccessorTypeResolver resolver) : + base(resolver, "java.lang.Win32Process") { } diff --git a/src/IKVM.Runtime/Java/Externs/java/lang/UNIXProcessEnvironment.cs b/src/IKVM.Runtime/Java/Externs/java/lang/UNIXProcessEnvironment.cs new file mode 100644 index 0000000000..0109d4653a --- /dev/null +++ b/src/IKVM.Runtime/Java/Externs/java/lang/UNIXProcessEnvironment.cs @@ -0,0 +1,52 @@ +using System; +using System.Runtime.InteropServices; + +using IKVM.Runtime.JNI; + +namespace IKVM.Java.Externs.java.lang +{ + + static class UNIXProcessEnvironment + { + +#if FIRST_PASS == false + + static global::ikvm.@internal.CallerID __callerID; + delegate IntPtr __jniDelegate__environ(IntPtr jniEnv); + static __jniDelegate__environ __jniPtr__environ; + +#endif + + /// + /// Implements the native method 'environ'. + /// + /// + public static object environ() + { +#if FIRST_PASS + throw new NotImplementedException(); +#else + __callerID ??= global::ikvm.@internal.CallerID.create(typeof(global::java.lang.UNIXProcessEnvironment).TypeHandle); + __jniPtr__environ ??= Marshal.GetDelegateForFunctionPointer<__jniDelegate__environ>(JNIFrame.GetFuncPtr(__callerID, "java/lang/ProcessEnvironment", nameof(environ), "()[[B")); + var jniFrm = new JNIFrame(); + var jniEnv = jniFrm.Enter(__callerID); + try + { + return jniFrm.UnwrapLocalRef(__jniPtr__environ(jniEnv)); + } + catch (Exception ex) + { + global::System.Console.WriteLine("*** exception in native code ***"); + global::System.Console.WriteLine(ex); + throw; + } + finally + { + jniFrm.Leave(); + } +#endif + } + + } + +} diff --git a/src/IKVM.Runtime/Java/Externs/java/lang/ProcessImpl.cs b/src/IKVM.Runtime/Java/Externs/java/lang/Win32Process.cs similarity index 95% rename from src/IKVM.Runtime/Java/Externs/java/lang/ProcessImpl.cs rename to src/IKVM.Runtime/Java/Externs/java/lang/Win32Process.cs index 777f47c6d9..816a96b821 100644 --- a/src/IKVM.Runtime/Java/Externs/java/lang/ProcessImpl.cs +++ b/src/IKVM.Runtime/Java/Externs/java/lang/Win32Process.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Security.AccessControl; using System.Text.RegularExpressions; using System.Threading; @@ -18,9 +17,9 @@ namespace IKVM.Java.Externs.java.lang { /// - /// Implements the native methods for 'ProcessImpl'. + /// Implements the native methods for 'Win32Process'. /// - static class ProcessImpl + static class Win32Process { #if FIRST_PASS == false @@ -45,7 +44,7 @@ static class ProcessImpl static MapEntryAccessor mapEntryAccessor; static SetAccessor setAccessor; static IteratorAccessor iteratorAccessor; - static ProcessImplAccessor processImplAccessor; + static Win32ProcessAccessor win32ProcessAccessor; static CallerIDAccessor CallerIDAccessor => JVM.Internal.BaseAccessors.Get(ref callerIDAccessor); @@ -87,7 +86,7 @@ static class ProcessImpl static IteratorAccessor IteratorAccessor => JVM.Internal.BaseAccessors.Get(ref iteratorAccessor); - static ProcessImplAccessor ProcessImplAccessor => JVM.Internal.BaseAccessors.Get(ref processImplAccessor); + static Win32ProcessAccessor Win32ProcessAccessor => JVM.Internal.BaseAccessors.Get(ref win32ProcessAccessor); #endif @@ -229,10 +228,7 @@ static object Create(string[] cmd, Dictionary env, string dir, S #if FIRST_PASS throw new NotImplementedException(); #else - if (RuntimeUtil.IsWindows) - return CreateWindows(cmd, env, dir, s0, s1, s2, redirectErrorStream); - else - return CreateUnix(cmd, env, dir, s0, s1, s2, redirectErrorStream); + return CreateWindows(cmd, env, dir, s0, s1, s2, redirectErrorStream); #endif } @@ -619,34 +615,6 @@ static bool WindowsExecutableExists(string file) } } - /// - /// Creates a new 'ProcessImpl' for a Unix machine. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - static object CreateUnix(string[] cmd, Dictionary env, string dir, Stream h0, Stream h1, Stream h2, bool redirectErrorStream) - { -#if NETFRAMEWORK - throw new NotImplementedException(); -#else - var psi = new ProcessStartInfo(); - psi.FileName = cmd[0]; - - foreach (var arg in cmd.Skip(1)) - psi.ArgumentList.Add(arg); - - return Create(psi, env, dir, h0, h1, h2, redirectErrorStream); -#endif - - } - /// /// Creates a new 'ProcessImpl' based on the filename and arguments configured on the . /// @@ -753,10 +721,10 @@ static object Create(ProcessStartInfo psi, Dictionary env, strin s0 = h0 == null ? ProcessBuilderNullOutputStreamAccessor.GetInstance() : BufferedOutputStreamAccessor.Init(FileOutputStreamAccessor.Init2(CreateFileDescriptor(h0))); s1 = h1 == null ? ProcessBuilderNullInputStreamAccessor.GetInstance() : BufferedInputStreamAccessor.Init(FileInputStreamAccessor.Init2(CreateFileDescriptor(h1))); s2 = h2 == null ? ProcessBuilderNullInputStreamAccessor.GetInstance() : FileInputStreamAccessor.Init2(CreateFileDescriptor(h2)); - }), null, CallerIDAccessor.InvokeCreate(ProcessImplAccessor.Type.TypeHandle)); + }), null, CallerIDAccessor.InvokeCreate(Win32ProcessAccessor.Type.TypeHandle)); // return new process - return ProcessImplAccessor.Init(process, s0, s1, s2); + return Win32ProcessAccessor.Init(process, s0, s1, s2); #endif } @@ -934,7 +902,7 @@ public static int waitFor(object self) #if FIRST_PASS throw new NotImplementedException(); #else - var pid = ProcessImplAccessor.GetProcess(self); + var pid = Win32ProcessAccessor.GetProcess(self); if (pid.HasExited) return pid.ExitCode; @@ -954,7 +922,7 @@ public static int exitValue(object self) #if FIRST_PASS throw new NotImplementedException(); #else - var pid = ProcessImplAccessor.GetProcess(self); + var pid = Win32ProcessAccessor.GetProcess(self); if (pid.HasExited == false) throw new global::java.lang.IllegalThreadStateException("process has not exited"); @@ -967,7 +935,7 @@ public static void destroy(object self) #if FIRST_PASS throw new NotImplementedException(); #else - var pid = ProcessImplAccessor.GetProcess(self); + var pid = Win32ProcessAccessor.GetProcess(self); if (pid.HasExited) return; diff --git a/src/IKVM.Tests/Java/java/lang/ProcessTests.cs b/src/IKVM.Tests/Java/java/lang/ProcessTests.cs index 0f94e0b841..07a17c5aef 100644 --- a/src/IKVM.Tests/Java/java/lang/ProcessTests.cs +++ b/src/IKVM.Tests/Java/java/lang/ProcessTests.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System.IO; +using System.Runtime.InteropServices; using FluentAssertions; @@ -14,6 +15,24 @@ namespace IKVM.Tests.Java.java.lang public class ProcessTests { + public TestContext TestContext { get; set; } + + [TestMethod] + public void CanReadExitCode() + { + string[] c; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + c = new[] { "cmd.exe", "/c", "exit 1" }; + else + c = new[] { "/bin/sh", "-c", "exit 1" }; + + var b = new ProcessBuilder(c); + var p = b.start(); + + p.waitFor(); + p.exitValue().Should().Be(1); + } + [TestMethod] public void CanReadFromInputStream() { @@ -24,6 +43,7 @@ public void CanReadFromInputStream() c = new[] { "/bin/sh", "-c", "echo hello" }; var b = new ProcessBuilder(c); + b.redirectOutput(ProcessBuilder.Redirect.PIPE); var p = b.start(); var r = new BufferedReader(new InputStreamReader(p.getInputStream())); @@ -43,6 +63,7 @@ public void CanReadFromErrorStream() c = new[] { "/bin/sh", "-c", "echo hello>&2" }; var b = new ProcessBuilder(c); + b.redirectError(ProcessBuilder.Redirect.PIPE); var p = b.start(); var r = new BufferedReader(new InputStreamReader(p.getErrorStream())); @@ -52,6 +73,105 @@ public void CanReadFromErrorStream() l.Should().Be("hello"); } + [TestMethod] + public void CanRedirectOutputToFile() + { + var f = Path.GetTempFileName(); + + string[] c; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + c = new[] { "cmd.exe", "/c", "echo hello" }; + else + c = new[] { "/bin/sh", "-c", "echo hello" }; + + var b = new ProcessBuilder(c); + b.redirectOutput(ProcessBuilder.Redirect.to(new global::java.io.File(f))); + var p = b.start(); + p.waitFor(); + + System.IO.File.ReadAllText(f).TrimEnd().Should().Be("hello"); + } + + [TestMethod] + public void CanRedirectErrorToFile() + { + var f = Path.GetTempFileName(); + + string[] c; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + c = new[] { "cmd.exe", "/c", "echo hello>&2" }; + else + c = new[] { "/bin/sh", "-c", "echo hello>&2" }; + + var b = new ProcessBuilder(c); + b.redirectError(ProcessBuilder.Redirect.to(new global::java.io.File(f))); + var p = b.start(); + p.waitFor(); + + System.IO.File.ReadAllText(f).TrimEnd().Should().Be("hello"); + } + + [TestMethod] + public void CanRedirectOutputToPipe() + { + string[] c; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + c = new[] { "cmd.exe", "/c", "echo hello" }; + else + c = new[] { "/bin/sh", "-c", "echo hello" }; + + var b = new ProcessBuilder(c); + b.redirectOutput(ProcessBuilder.Redirect.PIPE); + var p = b.start(); + p.waitFor(); + } + + [TestMethod] + public void CanRedirectErrorToPipe() + { + string[] c; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + c = new[] { "cmd.exe", "/c", "echo hello>&2" }; + else + c = new[] { "/bin/sh", "-c", "echo hello>&2" }; + + var b = new ProcessBuilder(c); + b.redirectError(ProcessBuilder.Redirect.PIPE); + var p = b.start(); + p.waitFor(); + } + + [TestMethod] + public void CanInheritInput() + { + string[] c; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + c = new[] { "cmd.exe", "/c", "echo hello" }; + else + c = new[] { "/bin/sh", "-c", "echo hello" }; + + var b = new ProcessBuilder(c); + b.redirectError(ProcessBuilder.Redirect.INHERIT); + var p = b.start(); + p.waitFor(); + TestContext.WriteLine(p.ToString()); + } + + [TestMethod] + public void CanInheritError() + { + string[] c; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + c = new[] { "cmd.exe", "/c", "echo hello" }; + else + c = new[] { "/bin/sh", "-c", "echo hello" }; + + var b = new ProcessBuilder(c); + b.redirectError(ProcessBuilder.Redirect.INHERIT); + var p = b.start(); + TestContext.WriteLine(p.ToString()); + } + [TestMethod] public void CanWaitForWithInheritIO() { diff --git a/src/libiava/libiava.clangproj b/src/libiava/libiava.clangproj index b4243cfd76..27ea6f3bd3 100644 --- a/src/libiava/libiava.clangproj +++ b/src/libiava/libiava.clangproj @@ -21,12 +21,15 @@ + + + @@ -49,12 +52,16 @@ + + + + + - diff --git a/src/libjvm/jni_md.h b/src/libjvm/jni_md.h new file mode 100644 index 0000000000..33d1ed4273 --- /dev/null +++ b/src/libjvm/jni_md.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 1997, 2011, 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. + */ + +/* Switch to the correct jni_md.h file without reliance on -I options. */ +#ifdef TARGET_ARCH_x86 +# include "jni_x86.h" +#endif +#ifdef TARGET_ARCH_sparc +# include "jni_sparc.h" +#endif +#ifdef TARGET_ARCH_zero +# include "jni_zero.h" +#endif +#ifdef TARGET_ARCH_arm +# include "jni_arm.h" +#endif +#ifdef TARGET_ARCH_aarch64 +# include "jni_arm.h" +#endif +#ifdef TARGET_ARCH_ppc +# include "jni_ppc.h" +#endif + + +/* + The local copies of JNI header files may be refreshed + from a JDK distribution by means of these commands: + + cp ${JDK_DIST}/solaris/include/solaris/jni_md.h ./jni_sparc.h + cp ${JDK_DIST}/win32/include/win32/jni_md.h ./jni_i486.h + cp ${JDK_DIST}/win32/include/jni.h ./jni.h + +*/ diff --git a/src/libjvm/jvm.cpp b/src/libjvm/jvm.cpp index d74e7ef75b..018dcfc005 100644 --- a/src/libjvm/jvm.cpp +++ b/src/libjvm/jvm.cpp @@ -1,6 +1,5 @@ #include #include -#include "jvm.h" #include #include @@ -21,6 +20,11 @@ #include #include #include +#include +#endif + +#ifdef __cplusplus +extern "C" { #endif #define assert(condition, fmt, ...); @@ -977,61 +981,57 @@ void* JNICALL JVM_FindLibraryEntry(void* handle, const char* name) } #ifdef WIN32 -void* JVM_GetThreadInterruptEvent() +void* JNICALL JVM_GetThreadInterruptEvent() { return 0; } #endif -#ifdef __cplusplus -extern "C" { -#endif - - int jio_vsnprintf(char* str, size_t count, const char* fmt, va_list args) - { - // Reject count values that are negative signed values converted to - // unsigned; see bug 4399518, 4417214 - if ((intptr_t)count <= 0) return -1; - - int result = vsnprintf(str, count, fmt, args); - if (result > 0 && (size_t)result >= count) { - result = -1; - } +int jio_vsnprintf(char* str, size_t count, const char* fmt, va_list args) +{ + // Reject count values that are negative signed values converted to + // unsigned; see bug 4399518, 4417214 + if ((intptr_t)count <= 0) return -1; - return result; + int result = vsnprintf(str, count, fmt, args); + if (result > 0 && (size_t)result >= count) { + result = -1; } - int jio_snprintf(char* str, size_t count, const char* fmt, ...) { - va_list args; - int len; - va_start(args, fmt); - len = jio_vsnprintf(str, count, fmt, args); - va_end(args); - return len; - } + return result; +} - int jio_fprintf(FILE* f, const char* fmt, ...) { - int len; - va_list args; - va_start(args, fmt); - len = jio_vfprintf(f, fmt, args); - va_end(args); - return len; - } +int jio_snprintf(char* str, size_t count, const char* fmt, ...) { + va_list args; + int len; + va_start(args, fmt); + len = jio_vsnprintf(str, count, fmt, args); + va_end(args); + return len; +} - int jio_vfprintf(FILE* f, const char* fmt, va_list args) - { - return vfprintf(f, fmt, args); - } +int jio_fprintf(FILE* f, const char* fmt, ...) { + int len; + va_list args; + va_start(args, fmt); + len = jio_vfprintf(f, fmt, args); + va_end(args); + return len; +} - int jio_printf(const char* fmt, ...) { - int len; - va_list args; - va_start(args, fmt); - len = jio_vfprintf(stdout, fmt, args); - va_end(args); - return len; - } +int jio_vfprintf(FILE* f, const char* fmt, va_list args) +{ + return vfprintf(f, fmt, args); +} + +int jio_printf(const char* fmt, ...) { + int len; + va_list args; + va_start(args, fmt); + len = jio_vfprintf(stdout, fmt, args); + va_end(args); + return len; +} #ifdef __cplusplus } diff --git a/src/libjvm/jvm.h b/src/libjvm/jvm.h deleted file mode 100644 index 681c648c54..0000000000 --- a/src/libjvm/jvm.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - -We use this file to redefine a few methods that should be exported, but which are not exported in jni.h. -OpenJDK passes /export or -export to the command line to do these manually. - -*/ - -#include - -#ifndef _IKVM_JVM_H_ -#define _IKVM_JVM_H_ - -#ifdef __cplusplus -extern "C" { -#endif - - JNIEXPORT int jio_vsnprintf(char* str, size_t count, const char* fmt, va_list args); - - JNIEXPORT int jio_snprintf(char* str, size_t count, const char* fmt, ...); - - JNIEXPORT int jio_fprintf(FILE*, const char* fmt, ...); - - JNIEXPORT int jio_vfprintf(FILE*, const char* fmt, va_list args); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/libjvm/libjvm.clangproj b/src/libjvm/libjvm.clangproj index 002fc7a938..2646f5ef9a 100644 --- a/src/libjvm/libjvm.clangproj +++ b/src/libjvm/libjvm.clangproj @@ -6,19 +6,42 @@ jvm + $(AdditionalCompileOptions);-Wno-dll-attribute-on-redeclaration - - - + + + + + + + + + + + + + + + + + + + + + + + + +