提交 095ce48d 编写于 作者: M martin

4960438: (process) Need IO redirection API for subprocesses

Reviewed-by: alanb, iris
上级 6d73e05f
...@@ -41,18 +41,24 @@ import java.io.*; ...@@ -41,18 +41,24 @@ import java.io.*;
* <p>The methods that create processes may not work well for special * <p>The methods that create processes may not work well for special
* processes on certain native platforms, such as native windowing * processes on certain native platforms, such as native windowing
* processes, daemon processes, Win16/DOS processes on Microsoft * processes, daemon processes, Win16/DOS processes on Microsoft
* Windows, or shell scripts. The created subprocess does not have * Windows, or shell scripts.
* its own terminal or console. All its standard I/O (i.e. stdin, *
* stdout, stderr) operations will be redirected to the parent process * <p>By default, the created subprocess does not have its own terminal
* through three streams * or console. All its standard I/O (i.e. stdin, stdout, stderr)
* ({@link #getOutputStream()}, * operations will be redirected to the parent process, where they can
* {@link #getInputStream()}, * be accessed via the streams obtained using the methods
* {@link #getErrorStream()}). * {@link #getOutputStream()},
* {@link #getInputStream()}, and
* {@link #getErrorStream()}.
* The parent process uses these streams to feed input to and get output * The parent process uses these streams to feed input to and get output
* from the subprocess. Because some native platforms only provide * from the subprocess. Because some native platforms only provide
* limited buffer size for standard input and output streams, failure * limited buffer size for standard input and output streams, failure
* to promptly write the input stream or read the output stream of * to promptly write the input stream or read the output stream of
* the subprocess may cause the subprocess to block, and even deadlock. * the subprocess may cause the subprocess to block, or even deadlock.
*
* <p>Where desired, <a href="ProcessBuilder.html#redirect-input">
* subprocess I/O can also be redirected</a>
* using methods of the {@link ProcessBuilder} class.
* *
* <p>The subprocess is not killed when there are no more references to * <p>The subprocess is not killed when there are no more references to
* the {@code Process} object, but rather the subprocess * the {@code Process} object, but rather the subprocess
...@@ -62,16 +68,22 @@ import java.io.*; ...@@ -62,16 +68,22 @@ import java.io.*;
* Process} object execute asynchronously or concurrently with respect * Process} object execute asynchronously or concurrently with respect
* to the Java process that owns the {@code Process} object. * to the Java process that owns the {@code Process} object.
* *
* @author unascribed * <p>As of 1.5, {@link ProcessBuilder#start()} is the preferred way
* @see ProcessBuilder * to create a {@code Process}.
*
* @since JDK1.0 * @since JDK1.0
*/ */
public abstract class Process { public abstract class Process {
/** /**
* Returns the output stream connected to the normal input of the * Returns the output stream connected to the normal input of the
* subprocess. Output to the stream is piped into the standard * subprocess. Output to the stream is piped into the standard
* input stream of the process represented by this {@code Process} * input of the process represented by this {@code Process} object.
* object. *
* <p>If the standard input of the subprocess has been redirected using
* {@link ProcessBuilder#redirectInput(Redirect)
* ProcessBuilder.redirectInput}
* then this method will return a
* <a href="ProcessBuilder.html#redirect-input">null output stream</a>.
* *
* <p>Implementation note: It is a good idea for the returned * <p>Implementation note: It is a good idea for the returned
* output stream to be buffered. * output stream to be buffered.
...@@ -84,30 +96,47 @@ public abstract class Process { ...@@ -84,30 +96,47 @@ public abstract class Process {
/** /**
* Returns the input stream connected to the normal output of the * Returns the input stream connected to the normal output of the
* subprocess. The stream obtains data piped from the standard * subprocess. The stream obtains data piped from the standard
* output stream of the process represented by this {@code * output of the process represented by this {@code Process} object.
* Process} object. *
* <p>If the standard output of the subprocess has been redirected using
* {@link ProcessBuilder#redirectOutput(Redirect)
* ProcessBuilder.redirectOutput}
* then this method will return a
* <a href="ProcessBuilder.html#redirect-output">null input stream</a>.
*
* <p>Otherwise, if the standard error of the subprocess has been
* redirected using
* {@link ProcessBuilder#redirectErrorStream(boolean)
* ProcessBuilder.redirectErrorStream}
* then the input stream returned by this method will receive the
* merged standard output and the standard error of the subprocess.
* *
* <p>Implementation note: It is a good idea for the returned * <p>Implementation note: It is a good idea for the returned
* input stream to be buffered. * input stream to be buffered.
* *
* @return the input stream connected to the normal output of the * @return the input stream connected to the normal output of the
* subprocess * subprocess
* @see ProcessBuilder#redirectErrorStream()
*/ */
abstract public InputStream getInputStream(); abstract public InputStream getInputStream();
/** /**
* Returns the input stream connected to the error output stream of * Returns the input stream connected to the error output of the
* the subprocess. The stream obtains data piped from the error * subprocess. The stream obtains data piped from the error output
* output stream of the process represented by this {@code Process} * of the process represented by this {@code Process} object.
* object. *
* <p>If the standard error of the subprocess has been redirected using
* {@link ProcessBuilder#redirectError(Redirect)
* ProcessBuilder.redirectError} or
* {@link ProcessBuilder#redirectErrorStream(boolean)
* ProcessBuilder.redirectErrorStream}
* then this method will return a
* <a href="ProcessBuilder.html#redirect-output">null input stream</a>.
* *
* <p>Implementation note: It is a good idea for the returned * <p>Implementation note: It is a good idea for the returned
* input stream to be buffered. * input stream to be buffered.
* *
* @return the input stream connected to the error output stream of * @return the input stream connected to the error output of
* the subprocess * the subprocess
* @see ProcessBuilder#redirectErrorStream()
*/ */
abstract public InputStream getErrorStream(); abstract public InputStream getErrorStream();
......
...@@ -33,4 +33,8 @@ import java.io.FileDescriptor; ...@@ -33,4 +33,8 @@ import java.io.FileDescriptor;
public interface JavaIOFileDescriptorAccess { public interface JavaIOFileDescriptorAccess {
public void set(FileDescriptor obj, int fd); public void set(FileDescriptor obj, int fd);
public int get(FileDescriptor fd); public int get(FileDescriptor fd);
// Only valid on Windows
public void setHandle(FileDescriptor obj, long handle);
public long getHandle(FileDescriptor obj);
} }
...@@ -152,11 +152,19 @@ public final class FileDescriptor { ...@@ -152,11 +152,19 @@ public final class FileDescriptor {
public int get(FileDescriptor obj) { public int get(FileDescriptor obj) {
return obj.fd; return obj.fd;
} }
public void setHandle(FileDescriptor obj, long handle) {
throw new UnsupportedOperationException();
}
public long getHandle(FileDescriptor obj) {
throw new UnsupportedOperationException();
}
} }
); );
} }
// pacakge private methods used by FIS,FOS and RAF // package private methods used by FIS, FOS and RAF
int incrementAndGetUseCount() { int incrementAndGetUseCount() {
return useCount.incrementAndGet(); return useCount.incrementAndGet();
......
...@@ -26,7 +26,10 @@ ...@@ -26,7 +26,10 @@
package java.lang; package java.lang;
import java.io.IOException; import java.io.IOException;
import java.lang.Process; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.ProcessBuilder.Redirect;
import java.lang.ProcessBuilder.Redirect;
/** /**
* This class is for the exclusive use of ProcessBuilder.start() to * This class is for the exclusive use of ProcessBuilder.start() to
...@@ -36,6 +39,9 @@ import java.lang.Process; ...@@ -36,6 +39,9 @@ import java.lang.Process;
* @since 1.5 * @since 1.5
*/ */
final class ProcessImpl { final class ProcessImpl {
private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
private ProcessImpl() {} // Not instantiable private ProcessImpl() {} // Not instantiable
private static byte[] toCString(String s) { private static byte[] toCString(String s) {
...@@ -54,6 +60,7 @@ final class ProcessImpl { ...@@ -54,6 +60,7 @@ final class ProcessImpl {
static Process start(String[] cmdarray, static Process start(String[] cmdarray,
java.util.Map<String,String> environment, java.util.Map<String,String> environment,
String dir, String dir,
ProcessBuilder.Redirect[] redirects,
boolean redirectErrorStream) boolean redirectErrorStream)
throws IOException throws IOException
{ {
...@@ -78,11 +85,61 @@ final class ProcessImpl { ...@@ -78,11 +85,61 @@ final class ProcessImpl {
int[] envc = new int[1]; int[] envc = new int[1];
byte[] envBlock = ProcessEnvironment.toEnvironmentBlock(environment, envc); byte[] envBlock = ProcessEnvironment.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 = redirects[1].toFileOutputStream();
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 = redirects[2].toFileOutputStream();
std_fds[2] = fdAccess.get(f2.getFD());
}
}
return new UNIXProcess return new UNIXProcess
(toCString(cmdarray[0]), (toCString(cmdarray[0]),
argBlock, args.length, argBlock, args.length,
envBlock, envc[0], envBlock, envc[0],
toCString(dir), toCString(dir),
std_fds,
redirectErrorStream); 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(); }
}
}
} }
} }
/* /*
* Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1995-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -34,9 +34,9 @@ import java.io.*; ...@@ -34,9 +34,9 @@ import java.io.*;
*/ */
final class UNIXProcess extends Process { final class UNIXProcess extends Process {
private FileDescriptor stdin_fd; private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
private FileDescriptor stdout_fd; = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
private FileDescriptor stderr_fd;
private int pid; private int pid;
private int exitcode; private int exitcode;
private boolean hasExited; private boolean hasExited;
...@@ -48,14 +48,25 @@ final class UNIXProcess extends Process { ...@@ -48,14 +48,25 @@ final class UNIXProcess extends Process {
/* this is for the reaping thread */ /* this is for the reaping thread */
private native int waitForProcessExit(int pid); private native int waitForProcessExit(int pid);
/**
* Create a process using fork(2) and exec(2).
*
* @param std_fds array of file descriptors. Indexes 0, 1, and
* 2 correspond to standard input, standard output and
* standard error, respectively. On input, a value of -1
* means to create a pipe to connect child and parent
* processes. On output, a value which is not -1 is the
* parent pipe fd corresponding to the pipe which has
* been created. An element of this array is -1 on input
* if and only if it is <em>not</em> -1 on output.
* @return the pid of the subprocess
*/
private native int forkAndExec(byte[] prog, private native int forkAndExec(byte[] prog,
byte[] argBlock, int argc, byte[] argBlock, int argc,
byte[] envBlock, int envc, byte[] envBlock, int envc,
byte[] dir, byte[] dir,
boolean redirectErrorStream, int[] std_fds,
FileDescriptor stdin_fd, boolean redirectErrorStream)
FileDescriptor stdout_fd,
FileDescriptor stderr_fd)
throws IOException; throws IOException;
/* In the process constructor we wait on this gate until the process */ /* In the process constructor we wait on this gate until the process */
...@@ -100,11 +111,9 @@ final class UNIXProcess extends Process { ...@@ -100,11 +111,9 @@ final class UNIXProcess extends Process {
final byte[] argBlock, final int argc, final byte[] argBlock, final int argc,
final byte[] envBlock, final int envc, final byte[] envBlock, final int envc,
final byte[] dir, final byte[] dir,
final int[] std_fds,
final boolean redirectErrorStream) final boolean redirectErrorStream)
throws IOException { throws IOException {
stdin_fd = new FileDescriptor();
stdout_fd = new FileDescriptor();
stderr_fd = new FileDescriptor();
final Gate gate = new Gate(); final Gate gate = new Gate();
/* /*
...@@ -117,8 +126,8 @@ final class UNIXProcess extends Process { ...@@ -117,8 +126,8 @@ final class UNIXProcess extends Process {
*/ */
java.security.AccessController.doPrivileged( java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() { new java.security.PrivilegedAction<Void>() {
public Object run() { public Void run() {
Thread t = new Thread("process reaper") { Thread t = new Thread("process reaper") {
public void run() { public void run() {
try { try {
...@@ -126,24 +135,43 @@ final class UNIXProcess extends Process { ...@@ -126,24 +135,43 @@ final class UNIXProcess extends Process {
argBlock, argc, argBlock, argc,
envBlock, envc, envBlock, envc,
dir, dir,
redirectErrorStream, std_fds,
stdin_fd, stdout_fd, stderr_fd); redirectErrorStream);
} catch (IOException e) { } catch (IOException e) {
gate.setException(e); /*remember to rethrow later*/ gate.setException(e); /*remember to rethrow later*/
gate.exit(); gate.exit();
return; return;
} }
java.security.AccessController.doPrivileged( java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() { new java.security.PrivilegedAction<Void>() {
public Object run() { public Void run() {
stdin_stream = new BufferedOutputStream(new if (std_fds[0] == -1)
FileOutputStream(stdin_fd)); stdin_stream = new ProcessBuilder.NullOutputStream();
stdout_stream = new BufferedInputStream(new else {
FileInputStream(stdout_fd)); FileDescriptor stdin_fd = new FileDescriptor();
fdAccess.set(stdin_fd, std_fds[0]);
stdin_stream = new BufferedOutputStream(
new FileOutputStream(stdin_fd));
}
if (std_fds[1] == -1)
stdout_stream = new ProcessBuilder.NullInputStream();
else {
FileDescriptor stdout_fd = new FileDescriptor();
fdAccess.set(stdout_fd, std_fds[1]);
stdout_stream = new BufferedInputStream(
new FileInputStream(stdout_fd));
}
if (std_fds[2] == -1)
stderr_stream = new ProcessBuilder.NullInputStream();
else {
FileDescriptor stderr_fd = new FileDescriptor();
fdAccess.set(stderr_fd, std_fds[2]);
stderr_stream = new FileInputStream(stderr_fd); stderr_stream = new FileInputStream(stderr_fd);
return null;
} }
});
return null; }});
gate.exit(); /* exit from constructor */ gate.exit(); /* exit from constructor */
int res = waitForProcessExit(pid); int res = waitForProcessExit(pid);
synchronized (UNIXProcess.this) { synchronized (UNIXProcess.this) {
...@@ -155,9 +183,7 @@ final class UNIXProcess extends Process { ...@@ -155,9 +183,7 @@ final class UNIXProcess extends Process {
}; };
t.setDaemon(true); t.setDaemon(true);
t.start(); t.start();
return null; return null; }});
}
});
gate.waitForExit(); gate.waitForExit();
IOException e = gate.getException(); IOException e = gate.getException();
if (e != null) if (e != null)
......
/* /*
* Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1995-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -33,59 +33,85 @@ import java.io.*; ...@@ -33,59 +33,85 @@ import java.io.*;
*/ */
final class UNIXProcess extends Process { final class UNIXProcess extends Process {
private FileDescriptor stdin_fd; private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
private FileDescriptor stdout_fd; = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
private FileDescriptor stderr_fd;
private int pid; private final int pid;
private int exitcode; private int exitcode;
private boolean hasExited; private boolean hasExited;
private OutputStream stdin_stream; private OutputStream stdin_stream;
private BufferedInputStream stdout_stream; private InputStream stdout_stream;
private DeferredCloseInputStream stdout_inner_stream; private DeferredCloseInputStream stdout_inner_stream;
private DeferredCloseInputStream stderr_stream; private InputStream stderr_stream;
/* this is for the reaping thread */ /* this is for the reaping thread */
private native int waitForProcessExit(int pid); private native int waitForProcessExit(int pid);
/**
* Create a process using fork(2) and exec(2).
*
* @param std_fds array of file descriptors. Indexes 0, 1, and
* 2 correspond to standard input, standard output and
* standard error, respectively. On input, a value of -1
* means to create a pipe to connect child and parent
* processes. On output, a value which is not -1 is the
* parent pipe fd corresponding to the pipe which has
* been created. An element of this array is -1 on input
* if and only if it is <em>not</em> -1 on output.
* @return the pid of the subprocess
*/
private native int forkAndExec(byte[] prog, private native int forkAndExec(byte[] prog,
byte[] argBlock, int argc, byte[] argBlock, int argc,
byte[] envBlock, int envc, byte[] envBlock, int envc,
byte[] dir, byte[] dir,
boolean redirectErrorStream, int[] std_fds,
FileDescriptor stdin_fd, boolean redirectErrorStream)
FileDescriptor stdout_fd,
FileDescriptor stderr_fd)
throws IOException; throws IOException;
UNIXProcess(final byte[] prog, UNIXProcess(final byte[] prog,
final byte[] argBlock, int argc, final byte[] argBlock, int argc,
final byte[] envBlock, int envc, final byte[] envBlock, int envc,
final byte[] dir, final byte[] dir,
final int[] std_fds,
final boolean redirectErrorStream) final boolean redirectErrorStream)
throws IOException { throws IOException {
stdin_fd = new FileDescriptor();
stdout_fd = new FileDescriptor();
stderr_fd = new FileDescriptor();
pid = forkAndExec(prog, pid = forkAndExec(prog,
argBlock, argc, argBlock, argc,
envBlock, envc, envBlock, envc,
dir, dir,
redirectErrorStream, std_fds,
stdin_fd, stdout_fd, stderr_fd); redirectErrorStream);
java.security.AccessController.doPrivileged( java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() { new java.security.PrivilegedAction<Void>() { public Void run() {
public Object run() { if (std_fds[0] == -1)
stdin_stream stdin_stream = new ProcessBuilder.NullOutputStream();
= new BufferedOutputStream(new FileOutputStream(stdin_fd)); else {
FileDescriptor stdin_fd = new FileDescriptor();
fdAccess.set(stdin_fd, std_fds[0]);
stdin_stream = new BufferedOutputStream(
new FileOutputStream(stdin_fd));
}
if (std_fds[1] == -1)
stdout_stream = new ProcessBuilder.NullInputStream();
else {
FileDescriptor stdout_fd = new FileDescriptor();
fdAccess.set(stdout_fd, std_fds[1]);
stdout_inner_stream = new DeferredCloseInputStream(stdout_fd); stdout_inner_stream = new DeferredCloseInputStream(stdout_fd);
stdout_stream = new BufferedInputStream(stdout_inner_stream); stdout_stream = new BufferedInputStream(stdout_inner_stream);
}
if (std_fds[2] == -1)
stderr_stream = new ProcessBuilder.NullInputStream();
else {
FileDescriptor stderr_fd = new FileDescriptor();
fdAccess.set(stderr_fd, std_fds[2]);
stderr_stream = new DeferredCloseInputStream(stderr_fd); stderr_stream = new DeferredCloseInputStream(stderr_fd);
return null;
} }
});
return null; }});
/* /*
* For each subprocess forked a corresponding reaper thread * For each subprocess forked a corresponding reaper thread
...@@ -97,8 +123,7 @@ final class UNIXProcess extends Process { ...@@ -97,8 +123,7 @@ final class UNIXProcess extends Process {
*/ */
java.security.AccessController.doPrivileged( java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() { new java.security.PrivilegedAction<Void>() { public Void run() {
public Object run() {
Thread t = new Thread("process reaper") { Thread t = new Thread("process reaper") {
public void run() { public void run() {
int res = waitForProcessExit(pid); int res = waitForProcessExit(pid);
...@@ -111,9 +136,7 @@ final class UNIXProcess extends Process { ...@@ -111,9 +136,7 @@ final class UNIXProcess extends Process {
}; };
t.setDaemon(true); t.setDaemon(true);
t.start(); t.start();
return null; return null; }});
}
});
} }
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
...@@ -154,8 +177,11 @@ final class UNIXProcess extends Process { ...@@ -154,8 +177,11 @@ final class UNIXProcess extends Process {
destroyProcess(pid); destroyProcess(pid);
try { try {
stdin_stream.close(); stdin_stream.close();
if (stdout_inner_stream != null)
stdout_inner_stream.closeDeferred(stdout_stream); stdout_inner_stream.closeDeferred(stdout_stream);
stderr_stream.closeDeferred(stderr_stream); if (stderr_stream instanceof DeferredCloseInputStream)
((DeferredCloseInputStream) stderr_stream)
.closeDeferred(stderr_stream);
} catch (IOException e) { } catch (IOException e) {
// ignore // ignore
} }
......
/* /*
* Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1995-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -491,10 +491,8 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, ...@@ -491,10 +491,8 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
jbyteArray argBlock, jint argc, jbyteArray argBlock, jint argc,
jbyteArray envBlock, jint envc, jbyteArray envBlock, jint envc,
jbyteArray dir, jbyteArray dir,
jboolean redirectErrorStream, jintArray std_fds,
jobject stdin_fd, jboolean redirectErrorStream)
jobject stdout_fd,
jobject stderr_fd)
{ {
int errnum; int errnum;
int resultPid = -1; int resultPid = -1;
...@@ -505,6 +503,7 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, ...@@ -505,6 +503,7 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
const char *pargBlock = getBytes(env, argBlock); const char *pargBlock = getBytes(env, argBlock);
const char *penvBlock = getBytes(env, envBlock); const char *penvBlock = getBytes(env, envBlock);
const char *pdir = getBytes(env, dir); const char *pdir = getBytes(env, dir);
jint *fds = NULL;
in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1; in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1;
...@@ -527,9 +526,13 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, ...@@ -527,9 +526,13 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
initVectorFromBlock(envv, penvBlock, envc); initVectorFromBlock(envv, penvBlock, envc);
} }
if ((pipe(in) < 0) || assert(std_fds != NULL);
(pipe(out) < 0) || fds = (*env)->GetIntArrayElements(env, std_fds, NULL);
(pipe(err) < 0) || if (fds == NULL) goto Catch;
if ((fds[0] == -1 && pipe(in) < 0) ||
(fds[1] == -1 && pipe(out) < 0) ||
(fds[2] == -1 && pipe(err) < 0) ||
(pipe(fail) < 0)) { (pipe(fail) < 0)) {
throwIOException(env, errno, "Bad file descriptor"); throwIOException(env, errno, "Bad file descriptor");
goto Catch; goto Catch;
...@@ -544,23 +547,26 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, ...@@ -544,23 +547,26 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
if (resultPid == 0) { if (resultPid == 0) {
/* Child process */ /* Child process */
/* Close the parent sides of the pipe. /* Close the parent sides of the pipes.
Give the child sides of the pipes the right fileno's.
Closing pipe fds here is redundant, since closeDescriptors() Closing pipe fds here is redundant, since closeDescriptors()
would do it anyways, but a little paranoia is a good thing. */ would do it anyways, but a little paranoia is a good thing. */
closeSafely(in[1]);
closeSafely(out[0]);
closeSafely(err[0]);
closeSafely(fail[0]);
/* Give the child sides of the pipes the right fileno's. */
/* Note: it is possible for in[0] == 0 */ /* Note: it is possible for in[0] == 0 */
close(in[1]); moveDescriptor(in[0] != -1 ? in[0] : fds[0], STDIN_FILENO);
moveDescriptor(in[0], STDIN_FILENO); moveDescriptor(out[1]!= -1 ? out[1] : fds[1], STDOUT_FILENO);
close(out[0]);
moveDescriptor(out[1], STDOUT_FILENO);
close(err[0]);
if (redirectErrorStream) { if (redirectErrorStream) {
close(err[1]); closeSafely(err[1]);
dup2(STDOUT_FILENO, STDERR_FILENO); dup2(STDOUT_FILENO, STDERR_FILENO);
} else { } else {
moveDescriptor(err[1], STDERR_FILENO); moveDescriptor(err[1] != -1 ? err[1] : fds[2], STDERR_FILENO);
} }
close(fail[0]);
moveDescriptor(fail[1], FAIL_FILENO); moveDescriptor(fail[1], FAIL_FILENO);
/* close everything */ /* close everything */
...@@ -606,9 +612,9 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, ...@@ -606,9 +612,9 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
goto Catch; goto Catch;
} }
(*env)->SetIntField(env, stdin_fd, IO_fd_fdID, in [1]); fds[0] = (in [1] != -1) ? in [1] : -1;
(*env)->SetIntField(env, stdout_fd, IO_fd_fdID, out[0]); fds[1] = (out[0] != -1) ? out[0] : -1;
(*env)->SetIntField(env, stderr_fd, IO_fd_fdID, err[0]); fds[2] = (err[0] != -1) ? err[0] : -1;
Finally: Finally:
/* Always clean up the child's side of the pipes */ /* Always clean up the child's side of the pipes */
...@@ -628,6 +634,9 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, ...@@ -628,6 +634,9 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
releaseBytes(env, envBlock, penvBlock); releaseBytes(env, envBlock, penvBlock);
releaseBytes(env, dir, pdir); releaseBytes(env, dir, pdir);
if (fds != NULL)
(*env)->ReleaseIntArrayElements(env, std_fds, fds, 0);
return resultPid; return resultPid;
Catch: Catch:
......
...@@ -29,17 +29,14 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -29,17 +29,14 @@ import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Instances of the file descriptor class serve as an opaque handle * Instances of the file descriptor class serve as an opaque handle
* to the underlying machine-specific structure representing an open * to the underlying machine-specific structure representing an
* file, an open socket, or another source or sink of bytes. The * open file, an open socket, or another source or sink of bytes.
* main practical use for a file descriptor is to create a * The main practical use for a file descriptor is to create a
* <code>FileInputStream</code> or <code>FileOutputStream</code> to * {@link FileInputStream} or {@link FileOutputStream} to contain it.
* contain it. *
* <p> * <p>Applications should not create their own file descriptors.
* Applications should not create their own file descriptors.
* *
* @author Pavani Diwanji * @author Pavani Diwanji
* @see java.io.FileInputStream
* @see java.io.FileOutputStream
* @since JDK1.0 * @since JDK1.0
*/ */
public final class FileDescriptor { public final class FileDescriptor {
...@@ -81,6 +78,14 @@ public final class FileDescriptor { ...@@ -81,6 +78,14 @@ public final class FileDescriptor {
public int get(FileDescriptor obj) { public int get(FileDescriptor obj) {
return obj.fd; return obj.fd;
} }
public void setHandle(FileDescriptor obj, long handle) {
obj.handle = handle;
}
public long getHandle(FileDescriptor obj) {
return obj.handle;
}
} }
); );
} }
...@@ -88,7 +93,7 @@ public final class FileDescriptor { ...@@ -88,7 +93,7 @@ public final class FileDescriptor {
/** /**
* A handle to the standard input stream. Usually, this file * A handle to the standard input stream. Usually, this file
* descriptor is not used directly, but rather via the input stream * descriptor is not used directly, but rather via the input stream
* known as <code>System.in</code>. * known as {@code System.in}.
* *
* @see java.lang.System#in * @see java.lang.System#in
*/ */
...@@ -97,7 +102,7 @@ public final class FileDescriptor { ...@@ -97,7 +102,7 @@ public final class FileDescriptor {
/** /**
* A handle to the standard output stream. Usually, this file * A handle to the standard output stream. Usually, this file
* descriptor is not used directly, but rather via the output stream * descriptor is not used directly, but rather via the output stream
* known as <code>System.out</code>. * known as {@code System.out}.
* @see java.lang.System#out * @see java.lang.System#out
*/ */
public static final FileDescriptor out = standardStream(1); public static final FileDescriptor out = standardStream(1);
...@@ -105,7 +110,7 @@ public final class FileDescriptor { ...@@ -105,7 +110,7 @@ public final class FileDescriptor {
/** /**
* A handle to the standard error stream. Usually, this file * A handle to the standard error stream. Usually, this file
* descriptor is not used directly, but rather via the output stream * descriptor is not used directly, but rather via the output stream
* known as <code>System.err</code>. * known as {@code System.err}.
* *
* @see java.lang.System#err * @see java.lang.System#err
*/ */
...@@ -114,9 +119,9 @@ public final class FileDescriptor { ...@@ -114,9 +119,9 @@ public final class FileDescriptor {
/** /**
* Tests if this file descriptor object is valid. * Tests if this file descriptor object is valid.
* *
* @return <code>true</code> if the file descriptor object represents a * @return {@code true} if the file descriptor object represents a
* valid, open file, socket, or other active I/O connection; * valid, open file, socket, or other active I/O connection;
* <code>false</code> otherwise. * {@code false} otherwise.
*/ */
public boolean valid() { public boolean valid() {
return ((handle != -1) || (fd != -1)); return ((handle != -1) || (fd != -1));
......
...@@ -25,7 +25,16 @@ ...@@ -25,7 +25,16 @@
package java.lang; package java.lang;
import java.io.*; import java.io.IOException;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileDescriptor;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.lang.ProcessBuilder.Redirect;
/* This class is for the exclusive use of ProcessBuilder.start() to /* This class is for the exclusive use of ProcessBuilder.start() to
* create new processes. * create new processes.
...@@ -35,30 +44,82 @@ import java.io.*; ...@@ -35,30 +44,82 @@ import java.io.*;
*/ */
final class ProcessImpl extends Process { final class ProcessImpl extends Process {
private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
// System-dependent portion of ProcessBuilder.start() // System-dependent portion of ProcessBuilder.start()
static Process start(String cmdarray[], static Process start(String cmdarray[],
java.util.Map<String,String> environment, java.util.Map<String,String> environment,
String dir, String dir,
ProcessBuilder.Redirect[] redirects,
boolean redirectErrorStream) boolean redirectErrorStream)
throws IOException throws IOException
{ {
String envblock = ProcessEnvironment.toEnvironmentBlock(environment); String envblock = ProcessEnvironment.toEnvironmentBlock(environment);
return new ProcessImpl(cmdarray, envblock, dir, redirectErrorStream);
FileInputStream f0 = null;
FileOutputStream f1 = null;
FileOutputStream f2 = null;
try {
long[] stdHandles;
if (redirects == null) {
stdHandles = new long[] { -1L, -1L, -1L };
} else {
stdHandles = new long[3];
if (redirects[0] == Redirect.PIPE)
stdHandles[0] = -1L;
else if (redirects[0] == Redirect.INHERIT)
stdHandles[0] = fdAccess.getHandle(FileDescriptor.in);
else {
f0 = new FileInputStream(redirects[0].file());
stdHandles[0] = fdAccess.getHandle(f0.getFD());
}
if (redirects[1] == Redirect.PIPE)
stdHandles[1] = -1L;
else if (redirects[1] == Redirect.INHERIT)
stdHandles[1] = fdAccess.getHandle(FileDescriptor.out);
else {
f1 = redirects[1].toFileOutputStream();
stdHandles[1] = fdAccess.getHandle(f1.getFD());
}
if (redirects[2] == Redirect.PIPE)
stdHandles[2] = -1L;
else if (redirects[2] == Redirect.INHERIT)
stdHandles[2] = fdAccess.getHandle(FileDescriptor.err);
else {
f2 = redirects[2].toFileOutputStream();
stdHandles[2] = fdAccess.getHandle(f2.getFD());
}
}
return new ProcessImpl(cmdarray, envblock, dir,
stdHandles, 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(); }
}
}
} }
private long handle = 0; private long handle = 0;
private FileDescriptor stdin_fd;
private FileDescriptor stdout_fd;
private FileDescriptor stderr_fd;
private OutputStream stdin_stream; private OutputStream stdin_stream;
private InputStream stdout_stream; private InputStream stdout_stream;
private InputStream stderr_stream; private InputStream stderr_stream;
private ProcessImpl(String cmd[], private ProcessImpl(final String cmd[],
String envblock, final String envblock,
String path, final String path,
boolean redirectErrorStream) final long[] stdHandles,
final boolean redirectErrorStream)
throws IOException throws IOException
{ {
// Win32 CreateProcess requires cmd[0] to be normalized // Win32 CreateProcess requires cmd[0] to be normalized
...@@ -91,25 +152,39 @@ final class ProcessImpl extends Process { ...@@ -91,25 +152,39 @@ final class ProcessImpl extends Process {
} }
String cmdstr = cmdbuf.toString(); String cmdstr = cmdbuf.toString();
stdin_fd = new FileDescriptor(); handle = create(cmdstr, envblock, path,
stdout_fd = new FileDescriptor(); stdHandles, redirectErrorStream);
stderr_fd = new FileDescriptor();
handle = create(cmdstr, envblock, path, redirectErrorStream,
stdin_fd, stdout_fd, stderr_fd);
java.security.AccessController.doPrivileged( java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() { new java.security.PrivilegedAction<Void>() {
public Object run() { public Void run() {
stdin_stream = if (stdHandles[0] == -1L)
new BufferedOutputStream(new FileOutputStream(stdin_fd)); stdin_stream = new ProcessBuilder.NullOutputStream();
stdout_stream = else {
new BufferedInputStream(new FileInputStream(stdout_fd)); FileDescriptor stdin_fd = new FileDescriptor();
stderr_stream = fdAccess.setHandle(stdin_fd, stdHandles[0]);
new FileInputStream(stderr_fd); stdin_stream = new BufferedOutputStream(
return null; new FileOutputStream(stdin_fd));
}
if (stdHandles[1] == -1L)
stdout_stream = new ProcessBuilder.NullInputStream();
else {
FileDescriptor stdout_fd = new FileDescriptor();
fdAccess.setHandle(stdout_fd, stdHandles[1]);
stdout_stream = new BufferedInputStream(
new FileInputStream(stdout_fd));
} }
});
if (stdHandles[2] == -1L)
stderr_stream = new ProcessBuilder.NullInputStream();
else {
FileDescriptor stderr_fd = new FileDescriptor();
fdAccess.setHandle(stderr_fd, stdHandles[2]);
stderr_stream = new FileInputStream(stderr_fd);
}
return null; }});
} }
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
...@@ -150,13 +225,30 @@ final class ProcessImpl extends Process { ...@@ -150,13 +225,30 @@ final class ProcessImpl extends Process {
public void destroy() { terminateProcess(handle); } public void destroy() { terminateProcess(handle); }
private static native void terminateProcess(long handle); private static native void terminateProcess(long handle);
/**
* Create a process using the win32 function CreateProcess.
*
* @param cmdstr the Windows commandline
* @param envblock NUL-separated, double-NUL-terminated list of
* environment strings in VAR=VALUE form
* @param dir the working directory of the process, or null if
* inheriting the current directory from the parent process
* @param stdHandles array of windows HANDLEs. Indexes 0, 1, and
* 2 correspond to standard input, standard output and
* standard error, respectively. On input, a value of -1
* means to create a pipe to connect child and parent
* processes. On output, a value which is not -1 is the
* parent pipe handle corresponding to the pipe which has
* been created. An element of this array is -1 on input
* if and only if it is <em>not</em> -1 on output.
* @param redirectErrorStream redirectErrorStream attribute
* @return the native subprocess HANDLE returned by CreateProcess
*/
private static native long create(String cmdstr, private static native long create(String cmdstr,
String envblock, String envblock,
String dir, String dir,
boolean redirectErrorStream, long[] stdHandles,
FileDescriptor in_fd, boolean redirectErrorStream)
FileDescriptor out_fd,
FileDescriptor err_fd)
throws IOException; throws IOException;
private static native boolean closeHandle(long handle); private static native boolean closeHandle(long handle);
......
...@@ -125,7 +125,7 @@ win32Error(JNIEnv *env, const char *functionName) ...@@ -125,7 +125,7 @@ win32Error(JNIEnv *env, const char *functionName)
static void static void
closeSafely(HANDLE handle) closeSafely(HANDLE handle)
{ {
if (handle) if (handle != INVALID_HANDLE_VALUE)
CloseHandle(handle); CloseHandle(handle);
} }
...@@ -134,23 +134,22 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored, ...@@ -134,23 +134,22 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
jstring cmd, jstring cmd,
jstring envBlock, jstring envBlock,
jstring dir, jstring dir,
jboolean redirectErrorStream, jlongArray stdHandles,
jobject in_fd, jboolean redirectErrorStream)
jobject out_fd,
jobject err_fd)
{ {
HANDLE inRead = 0; HANDLE inRead = INVALID_HANDLE_VALUE;
HANDLE inWrite = 0; HANDLE inWrite = INVALID_HANDLE_VALUE;
HANDLE outRead = 0; HANDLE outRead = INVALID_HANDLE_VALUE;
HANDLE outWrite = 0; HANDLE outWrite = INVALID_HANDLE_VALUE;
HANDLE errRead = 0; HANDLE errRead = INVALID_HANDLE_VALUE;
HANDLE errWrite = 0; HANDLE errWrite = INVALID_HANDLE_VALUE;
SECURITY_ATTRIBUTES sa; SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
STARTUPINFO si; STARTUPINFO si;
LPTSTR pcmd = NULL; LPTSTR pcmd = NULL;
LPCTSTR pdir = NULL; LPCTSTR pdir = NULL;
LPVOID penvBlock = NULL; LPVOID penvBlock = NULL;
jlong *handles = NULL;
jlong ret = 0; jlong ret = 0;
OSVERSIONINFO ver; OSVERSIONINFO ver;
jboolean onNT = JNI_FALSE; jboolean onNT = JNI_FALSE;
...@@ -161,17 +160,6 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored, ...@@ -161,17 +160,6 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT) if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT)
onNT = JNI_TRUE; onNT = JNI_TRUE;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = TRUE;
if (!(CreatePipe(&inRead, &inWrite, &sa, PIPE_SIZE) &&
CreatePipe(&outRead, &outWrite, &sa, PIPE_SIZE) &&
CreatePipe(&errRead, &errWrite, &sa, PIPE_SIZE))) {
win32Error(env, "CreatePipe");
goto Catch;
}
assert(cmd != NULL); assert(cmd != NULL);
pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL); pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL);
if (pcmd == NULL) goto Catch; if (pcmd == NULL) goto Catch;
...@@ -189,19 +177,62 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored, ...@@ -189,19 +177,62 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
if (penvBlock == NULL) goto Catch; if (penvBlock == NULL) goto Catch;
} }
assert(stdHandles != NULL);
handles = (*env)->GetLongArrayElements(env, stdHandles, NULL);
if (handles == NULL) goto Catch;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
si.cb = sizeof(si); si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES; si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = inRead;
si.hStdOutput = outWrite;
si.hStdError = redirectErrorStream ? outWrite : errWrite;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = TRUE;
if (handles[0] != (jlong) -1) {
si.hStdInput = (HANDLE) handles[0];
handles[0] = (jlong) -1;
} else {
if (! CreatePipe(&inRead, &inWrite, &sa, PIPE_SIZE)) {
win32Error(env, "CreatePipe");
goto Catch;
}
si.hStdInput = inRead;
SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE); SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE);
handles[0] = (jlong) inWrite;
}
SetHandleInformation(si.hStdInput, HANDLE_FLAG_INHERIT, TRUE);
if (handles[1] != (jlong) -1) {
si.hStdOutput = (HANDLE) handles[1];
handles[1] = (jlong) -1;
} else {
if (! CreatePipe(&outRead, &outWrite, &sa, PIPE_SIZE)) {
win32Error(env, "CreatePipe");
goto Catch;
}
si.hStdOutput = outWrite;
SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE); SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE);
handles[1] = (jlong) outRead;
}
SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT, TRUE);
if (redirectErrorStream) {
si.hStdError = si.hStdOutput;
handles[2] = (jlong) -1;
} else if (handles[2] != (jlong) -1) {
si.hStdError = (HANDLE) handles[2];
handles[2] = (jlong) -1;
} else {
if (! CreatePipe(&errRead, &errWrite, &sa, PIPE_SIZE)) {
win32Error(env, "CreatePipe");
goto Catch;
}
si.hStdError = errWrite;
SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE); SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE);
handles[2] = (jlong) errRead;
if (redirectErrorStream) }
SetHandleInformation(errWrite, HANDLE_FLAG_INHERIT, FALSE); SetHandleInformation(si.hStdError, HANDLE_FLAG_INHERIT, TRUE);
if (onNT) if (onNT)
processFlag = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT; processFlag = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
...@@ -237,9 +268,6 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored, ...@@ -237,9 +268,6 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
CloseHandle(pi.hThread); CloseHandle(pi.hThread);
ret = (jlong)pi.hProcess; ret = (jlong)pi.hProcess;
(*env)->SetLongField(env, in_fd, IO_handle_fdID, (jlong)inWrite);
(*env)->SetLongField(env, out_fd, IO_handle_fdID, (jlong)outRead);
(*env)->SetLongField(env, err_fd, IO_handle_fdID, (jlong)errRead);
Finally: Finally:
/* Always clean up the child's side of the pipes */ /* Always clean up the child's side of the pipes */
...@@ -257,6 +285,9 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored, ...@@ -257,6 +285,9 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
else else
JNU_ReleaseStringPlatformChars(env, dir, (char *) penvBlock); JNU_ReleaseStringPlatformChars(env, dir, (char *) penvBlock);
} }
if (handles != NULL)
(*env)->ReleaseLongArrayElements(env, stdHandles, handles, 0);
return ret; return ret;
Catch: Catch:
......
...@@ -25,12 +25,15 @@ ...@@ -25,12 +25,15 @@
* @test * @test
* @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689 * @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689
* 5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313 * 5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313
* 6464154 6523983 6206031 * 6464154 6523983 6206031 4960438 6631352 6631966
* @summary Basic tests for Process and Environment Variable code * @summary Basic tests for Process and Environment Variable code
* @run main/othervm Basic * @run main/othervm Basic
* @author Martin Buchholz * @author Martin Buchholz
*/ */
import java.lang.ProcessBuilder.Redirect;
import static java.lang.ProcessBuilder.Redirect.*;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.security.*; import java.security.*;
...@@ -257,7 +260,29 @@ public class Basic { ...@@ -257,7 +260,29 @@ public class Basic {
public static class JavaChild { public static class JavaChild {
public static void main(String args[]) throws Throwable { public static void main(String args[]) throws Throwable {
String action = args[0]; String action = args[0];
if (action.equals("System.getenv(String)")) { if (action.equals("testIO")) {
String expected = "standard input";
char[] buf = new char[expected.length()+1];
int n = new InputStreamReader(System.in).read(buf,0,buf.length);
if (n != expected.length())
System.exit(5);
if (! new String(buf,0,n).equals(expected))
System.exit(5);
System.err.print("standard error");
System.out.print("standard output");
} else if (action.equals("testInheritIO")) {
List<String> childArgs = new ArrayList<String>(javaChildArgs);
childArgs.add("testIO");
ProcessBuilder pb = new ProcessBuilder(childArgs);
pb.inheritIO();
ProcessResults r = run(pb);
if (! r.out().equals(""))
System.exit(7);
if (! r.err().equals(""))
System.exit(8);
if (r.exitValue() != 0)
System.exit(9);
} else if (action.equals("System.getenv(String)")) {
String val = System.getenv(args[1]); String val = System.getenv(args[1]);
printUTF8(val == null ? "null" : val); printUTF8(val == null ? "null" : val);
} else if (action.equals("System.getenv(\\u1234)")) { } else if (action.equals("System.getenv(\\u1234)")) {
...@@ -599,6 +624,333 @@ public class Basic { ...@@ -599,6 +624,333 @@ public class Basic {
} catch (Throwable t) { unexpected(t); } } catch (Throwable t) { unexpected(t); }
} }
static void checkRedirects(ProcessBuilder pb,
Redirect in, Redirect out, Redirect err) {
equal(pb.redirectInput(), in);
equal(pb.redirectOutput(), out);
equal(pb.redirectError(), err);
}
static void redirectIO(ProcessBuilder pb,
Redirect in, Redirect out, Redirect err) {
pb.redirectInput(in);
pb.redirectOutput(out);
pb.redirectError(err);
}
static void setFileContents(File file, String contents) {
try {
Writer w = new FileWriter(file);
w.write(contents);
w.close();
} catch (Throwable t) { unexpected(t); }
}
static String fileContents(File file) {
try {
Reader r = new FileReader(file);
StringBuilder sb = new StringBuilder();
char[] buffer = new char[1024];
int n;
while ((n = r.read(buffer)) != -1)
sb.append(buffer,0,n);
r.close();
return new String(sb);
} catch (Throwable t) { unexpected(t); return ""; }
}
static void testIORedirection() throws Throwable {
final File ifile = new File("ifile");
final File ofile = new File("ofile");
final File efile = new File("efile");
ifile.delete();
ofile.delete();
efile.delete();
//----------------------------------------------------------------
// Check mutual inequality of different types of Redirect
//----------------------------------------------------------------
Redirect[] redirects =
{ PIPE,
INHERIT,
Redirect.from(ifile),
Redirect.to(ifile),
Redirect.appendTo(ifile),
Redirect.from(ofile),
Redirect.to(ofile),
Redirect.appendTo(ofile),
};
for (int i = 0; i < redirects.length; i++)
for (int j = 0; j < redirects.length; j++)
equal(redirects[i].equals(redirects[j]), (i == j));
//----------------------------------------------------------------
// Check basic properties of different types of Redirect
//----------------------------------------------------------------
equal(PIPE.type(), Redirect.Type.PIPE);
equal(PIPE.toString(), "PIPE");
equal(PIPE.file(), null);
equal(INHERIT.type(), Redirect.Type.INHERIT);
equal(INHERIT.toString(), "INHERIT");
equal(INHERIT.file(), null);
equal(Redirect.from(ifile).type(), Redirect.Type.READ);
equal(Redirect.from(ifile).toString(),
"redirect to read from file \"ifile\"");
equal(Redirect.from(ifile).file(), ifile);
equal(Redirect.from(ifile),
Redirect.from(ifile));
equal(Redirect.from(ifile).hashCode(),
Redirect.from(ifile).hashCode());
equal(Redirect.to(ofile).type(), Redirect.Type.WRITE);
equal(Redirect.to(ofile).toString(),
"redirect to write to file \"ofile\"");
equal(Redirect.to(ofile).file(), ofile);
equal(Redirect.to(ofile),
Redirect.to(ofile));
equal(Redirect.to(ofile).hashCode(),
Redirect.to(ofile).hashCode());
equal(Redirect.appendTo(ofile).type(), Redirect.Type.APPEND);
equal(Redirect.appendTo(efile).toString(),
"redirect to append to file \"efile\"");
equal(Redirect.appendTo(efile).file(), efile);
equal(Redirect.appendTo(efile),
Redirect.appendTo(efile));
equal(Redirect.appendTo(efile).hashCode(),
Redirect.appendTo(efile).hashCode());
//----------------------------------------------------------------
// Check initial values of redirects
//----------------------------------------------------------------
List<String> childArgs = new ArrayList<String>(javaChildArgs);
childArgs.add("testIO");
final ProcessBuilder pb = new ProcessBuilder(childArgs);
checkRedirects(pb, PIPE, PIPE, PIPE);
//----------------------------------------------------------------
// Check inheritIO
//----------------------------------------------------------------
pb.inheritIO();
checkRedirects(pb, INHERIT, INHERIT, INHERIT);
//----------------------------------------------------------------
// Check setters and getters agree
//----------------------------------------------------------------
pb.redirectInput(ifile);
equal(pb.redirectInput().file(), ifile);
equal(pb.redirectInput(), Redirect.from(ifile));
pb.redirectOutput(ofile);
equal(pb.redirectOutput().file(), ofile);
equal(pb.redirectOutput(), Redirect.to(ofile));
pb.redirectError(efile);
equal(pb.redirectError().file(), efile);
equal(pb.redirectError(), Redirect.to(efile));
THROWS(IllegalArgumentException.class,
new Fun(){void f() {
pb.redirectInput(Redirect.to(ofile)); }},
new Fun(){void f() {
pb.redirectInput(Redirect.appendTo(ofile)); }},
new Fun(){void f() {
pb.redirectOutput(Redirect.from(ifile)); }},
new Fun(){void f() {
pb.redirectError(Redirect.from(ifile)); }});
THROWS(IOException.class,
// Input file does not exist
new Fun(){void f() throws Throwable { pb.start(); }});
setFileContents(ifile, "standard input");
//----------------------------------------------------------------
// Writing to non-existent files
//----------------------------------------------------------------
{
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile), "standard output");
equal(fileContents(efile), "standard error");
equal(r.out(), "");
equal(r.err(), "");
ofile.delete();
efile.delete();
}
//----------------------------------------------------------------
// Both redirectErrorStream + redirectError
//----------------------------------------------------------------
{
pb.redirectErrorStream(true);
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile),
"standard error" + "standard output");
equal(fileContents(efile), "");
equal(r.out(), "");
equal(r.err(), "");
ofile.delete();
efile.delete();
}
//----------------------------------------------------------------
// Appending to existing files
//----------------------------------------------------------------
{
setFileContents(ofile, "ofile-contents");
setFileContents(efile, "efile-contents");
pb.redirectOutput(Redirect.appendTo(ofile));
pb.redirectError(Redirect.appendTo(efile));
pb.redirectErrorStream(false);
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile),
"ofile-contents" + "standard output");
equal(fileContents(efile),
"efile-contents" + "standard error");
equal(r.out(), "");
equal(r.err(), "");
ofile.delete();
efile.delete();
}
//----------------------------------------------------------------
// Replacing existing files
//----------------------------------------------------------------
{
setFileContents(ofile, "ofile-contents");
setFileContents(efile, "efile-contents");
pb.redirectOutput(ofile);
pb.redirectError(Redirect.to(efile));
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile), "standard output");
equal(fileContents(efile), "standard error");
equal(r.out(), "");
equal(r.err(), "");
ofile.delete();
efile.delete();
}
//----------------------------------------------------------------
// Appending twice to the same file?
//----------------------------------------------------------------
{
setFileContents(ofile, "ofile-contents");
setFileContents(efile, "efile-contents");
Redirect appender = Redirect.appendTo(ofile);
pb.redirectOutput(appender);
pb.redirectError(appender);
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile),
"ofile-contents" +
"standard error" +
"standard output");
equal(fileContents(efile), "efile-contents");
equal(r.out(), "");
equal(r.err(), "");
ifile.delete();
ofile.delete();
efile.delete();
}
//----------------------------------------------------------------
// Testing INHERIT is harder.
// Note that this requires __FOUR__ nested JVMs involved in one test,
// if you count the harness JVM.
//----------------------------------------------------------------
{
redirectIO(pb, PIPE, PIPE, PIPE);
List<String> command = pb.command();
command.set(command.size() - 1, "testInheritIO");
Process p = pb.start();
new PrintStream(p.getOutputStream()).print("standard input");
p.getOutputStream().close();
ProcessResults r = run(p);
equal(r.exitValue(), 0);
equal(r.out(), "standard output");
equal(r.err(), "standard error");
}
//----------------------------------------------------------------
// Test security implications of I/O redirection
//----------------------------------------------------------------
// Read access to current directory is always granted;
// So create a tmpfile for input instead.
final File tmpFile = File.createTempFile("Basic", "tmp");
setFileContents(tmpFile, "standard input");
final Policy policy = new Policy();
Policy.setPolicy(policy);
System.setSecurityManager(new SecurityManager());
try {
final Permission xPermission
= new FilePermission("<<ALL FILES>>", "execute");
final Permission rxPermission
= new FilePermission("<<ALL FILES>>", "read,execute");
final Permission wxPermission
= new FilePermission("<<ALL FILES>>", "write,execute");
final Permission rwxPermission
= new FilePermission("<<ALL FILES>>", "read,write,execute");
THROWS(SecurityException.class,
new Fun() { void f() throws IOException {
policy.setPermissions(xPermission);
redirectIO(pb, from(tmpFile), PIPE, PIPE);
pb.start();}},
new Fun() { void f() throws IOException {
policy.setPermissions(rxPermission);
redirectIO(pb, PIPE, to(ofile), PIPE);
pb.start();}},
new Fun() { void f() throws IOException {
policy.setPermissions(rxPermission);
redirectIO(pb, PIPE, PIPE, to(efile));
pb.start();}});
{
policy.setPermissions(rxPermission);
redirectIO(pb, from(tmpFile), PIPE, PIPE);
ProcessResults r = run(pb);
equal(r.out(), "standard output");
equal(r.err(), "standard error");
}
{
policy.setPermissions(wxPermission);
redirectIO(pb, PIPE, to(ofile), to(efile));
Process p = pb.start();
new PrintStream(p.getOutputStream()).print("standard input");
p.getOutputStream().close();
ProcessResults r = run(p);
policy.setPermissions(rwxPermission);
equal(fileContents(ofile), "standard output");
equal(fileContents(efile), "standard error");
}
{
policy.setPermissions(rwxPermission);
redirectIO(pb, from(tmpFile), to(ofile), to(efile));
ProcessResults r = run(pb);
policy.setPermissions(rwxPermission);
equal(fileContents(ofile), "standard output");
equal(fileContents(efile), "standard error");
}
} finally {
policy.setPermissions(new RuntimePermission("setSecurityManager"));
System.setSecurityManager(null);
tmpFile.delete();
ifile.delete();
ofile.delete();
efile.delete();
}
}
private static void realMain(String[] args) throws Throwable { private static void realMain(String[] args) throws Throwable {
if (Windows.is()) if (Windows.is())
System.out.println("This appears to be a Windows system."); System.out.println("This appears to be a Windows system.");
...@@ -607,6 +959,9 @@ public class Basic { ...@@ -607,6 +959,9 @@ public class Basic {
if (UnicodeOS.is()) if (UnicodeOS.is())
System.out.println("This appears to be a Unicode-based OS."); System.out.println("This appears to be a Unicode-based OS.");
try { testIORedirection(); }
catch (Throwable t) { unexpected(t); }
//---------------------------------------------------------------- //----------------------------------------------------------------
// Basic tests for setting, replacing and deleting envvars // Basic tests for setting, replacing and deleting envvars
//---------------------------------------------------------------- //----------------------------------------------------------------
...@@ -1354,7 +1709,8 @@ public class Basic { ...@@ -1354,7 +1709,8 @@ public class Basic {
execPermission); execPermission);
ProcessBuilder pb = new ProcessBuilder("env"); ProcessBuilder pb = new ProcessBuilder("env");
pb.environment().put("foo","bar"); pb.environment().put("foo","bar");
pb.start(); Process p = pb.start();
closeStreams(p);
} catch (IOException e) { // OK } catch (IOException e) { // OK
} catch (Throwable t) { unexpected(t); } } catch (Throwable t) { unexpected(t); }
...@@ -1378,6 +1734,14 @@ public class Basic { ...@@ -1378,6 +1734,14 @@ public class Basic {
} }
static void closeStreams(Process p) {
try {
p.getOutputStream().close();
p.getInputStream().close();
p.getErrorStream().close();
} catch (Throwable t) { unexpected(t); }
}
//---------------------------------------------------------------- //----------------------------------------------------------------
// A Policy class designed to make permissions fiddling very easy. // A Policy class designed to make permissions fiddling very easy.
//---------------------------------------------------------------- //----------------------------------------------------------------
...@@ -1432,10 +1796,19 @@ public class Basic { ...@@ -1432,10 +1796,19 @@ public class Basic {
} }
} catch (Throwable t) { } catch (Throwable t) {
throwable = t; throwable = t;
} finally {
try { is.close(); }
catch (Throwable t) { throwable = t; }
} }
} }
} }
static ProcessResults run(ProcessBuilder pb) {
try {
return run(pb.start());
} catch (Throwable t) { unexpected(t); return null; }
}
private static ProcessResults run(Process p) { private static ProcessResults run(Process p) {
Throwable throwable = null; Throwable throwable = null;
int exitValue = -1; int exitValue = -1;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册