Name: bsT130419 Date: 09/25/2001
java version "1.4.0-beta2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-beta2-b77)
Java HotSpot(TM) Client VM (build 1.4.0-beta2-b77, mixed mode)
The problem is (as far as I have investigated) Win32-specific. All testing was
performed under Windows 98 (first edition). It involves an interaction between
the code in the public class java.lang.Runtime (unchanged for JDK 1.4) and a
change made in JDK 1.4 to the package-private class java.lang.Win32Process.
In java.lang.Runtime, we are concerned with the difference between exec()
functions that accept the command to execute as a single String and those which
accept a String[]. Each of these functions eventually (in Win32 environments)
result in calls to the Win32Process class. The difference we are concerned with
has to do with the fact that the Runtime.exec() functions that accept a single
command string first tokenize that string into an array of substrings, then
they call the exec() function which accepts an array, in order to prevent the
duplication of code. Nothing wrong with that, but it is this "pre-tokenizing"
of commands specified as a single string rather than an array which is part of
the interaction problem. The tokenizer employed is a standard StringTokenizer
that uses the default delimiter set, which is " \t\n\r\f": the space character,
the tab character, the newline character, the carriage-return character, and
the form-feed character. The delimiter characters themselves are not treated as
tokens. As a result of this pre-tokenizing operation, a command string such as
the following:
C:\PROGRAM FILES\JAVASOFT\JRE\1.4\bin\java
would be converted into the argument array:
element 0> C:\PROGRAM
element 1> FILES\JAVASOFT\JRE\1.4\bin\java
Note that the space between "PROGRAM" and "FILES" is lost. Quoting the command
string, as in:
"C:\PROGRAM FILES\JAVASOFT\JRE\1.4\bin\java"
would result in the argument array:
element 0> "C:\PROGRAM
element 1> FILES\JAVASOFT\JRE\1.4\bin\java"
Again, the intervening space is lost. Note that this behavior does not occur
when calling the exec() functions that accept an argument array. With those
functions, the original command string is not pre-tokenized, resulting in:
element 0> C:\PROGRAM FILES\JAVASOFT\JRE\1.4\bin\java
or:
element 0> "C:\PROGRAM FILES\JAVASOFT\JRE\1.4\bin\java"
The other part of the interaction problem occurs in java.lang.Win32Process. In
the constructor of this object, any necessary quoting of arguments that contain
spaces is performed. If no spaces are present, the command line is reassembled
as is from the argument array, adding a space between each argument. So the
following array:
element 0> C:\PROGRAM
element 1> FILES\JAVASOFT\JRE\1.4\bin\java
would become:
C:\PROGRAM FILES\JAVASOFT\JRE\1.4\bin\java
and the quoted version:
element 0> "C:\PROGRAM
element 1> FILES\JAVASOFT\JRE\1.4\bin\java"
would become:
"C:\PROGRAM FILES\JAVASOFT\JRE\1.4\bin\java"
thereby fixing the problem produced earlier and reconstituing the original
command line. Quoted or not, the resulting string apparently works fine when it
comes time to create a Win32 process. This is all great for arguments that do
not contain spaces, and works the same in JDK 1.3.1 or JDK 1.4. The difference
is what happens when arguments do contain spaces. Prior to JDK 1.4, such
arguments were quoted in their entirety (unless they were already quoted), so
that the array:
element 0> C:\PROGRAM FILES\JAVASOFT\JRE\1.4\bin\java
would become:
"C:\PROGRAM FILES\JAVASOFT\JRE\1.4\bin\java"
Arguments already quoted were left as is. In JDK 1.4, arguments already quoted
are still left as is, but unquoted arguments that contain spaces are treated
different than they were previously. Such arguments have each region of one or
more spaces quoted individually, so that the array:
element 0> C:\PROGRAM FILES\JAVASOFT\JRE\1.4\bin\java
would become:
C:\PROGRAM" "FILES\JAVASOFT\JRE\1.4\bin\java
Unfortunately, a string of this type does not seen to make the Win32
CreateProcess() API happy. When it fails, the exception that is eventually
returned includes the API error code, but the returned error code is 0 (no
error) because the native Win32 module (Win32Process_md.c) that makes the call
does not save the value of the GetLastError() API before calling another API
(CloseHandle()) which succeeds, thereby resetting the associated error value.
Nevertheless, regardless of the actual error description, it is clearly the
result of using this type of command string, as can be seen from examining and
running the following test program:
import java.io.*;
public class ExecTest
{
public static void main(String[] args) {
try {
// Section 1 - Works in JDK 1.3.1 and 1.4
// String command = System.getProperty("java.home");
// command += File.separator + "bin" + File.separator
+ "java";
// Section 2 - Works in JDK 1.3.1 but causes Runtime.exec() to *FAIL* in 1.4
String[] command = new String[1];
command[0] = System.getProperty("java.home");
command[0] += File.separator + "bin" + File.separator
+ "java";
// Section 3 - Works in JDK 1.3.1 and 1.4
// String[] command = new String[1];
// command[0] = "\"";
// command[0] += System.getProperty("java.home");
// command[0] += File.separator + "bin" + File.separator
+ "java";
// command[0] += "\"";
Process process = Runtime.getRuntime().exec(command);
BufferedReader procOut = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String strOut = procOut.readLine();
while (strOut != null) {
System.out.println(strOut);
strOut = procOut.readLine();
}
process.waitFor();
System.exit(0);
} catch (Exception e) {
e.printStackTrace(System.out);
System.exit(1);
}
}
}
Compiling and running the test program under Windows using JDK 1.3.1 produces
no error, just the standard output from a successfully launched JVM with no
arguments. However, compiling and running the test program under JDK 1.4 (using
a JRE path that includes spaces) results in an execption similar to the
following:
java.io.IOException: CreateProcess: C:\PROGRAM" "FILES\JAVASOFT\JRE\1.4
\bin\java error=0
at java.lang.Win32Process.create(Native Method)
at java.lang.Win32Process.<init>(Win32Process.java:72)
at java.lang.Runtime.execInternal(Native Method)
at java.lang.Runtime.exec(Runtime.java:551)
at java.lang.Runtime.exec(Runtime.java:477)
at java.lang.Runtime.exec(Runtime.java:443)
at ExecTest.main(ExecTest.java:26)
Looking at the source of the test program, if you comment out Section 2 and
uncomment Section 1 or Section 3, you can see that either using a single
command string or quoting each array element in its entirety will avoid the
problem that leads to the CreateProcess() failure. Also, using a JVM located in
a path that does not contain spaces will also avoid the problem.
SO, why is this problem such a big deal, you ask? Why can't we just either
avoid the Runtime.exec() functions that accept an command string array, or make
sure we quote our paths under Win32? Well, we can do those things in our own
code, and in any code we can modify, but we can't reasonably fix RMID.
Specifically, sun.rmi.server.Activation.initCommand(), which creates the
command string array that will eventually be passed to Runtime.exec() to create
the process for each RMI activation group. This command string array is not
quoted (it uses the same technique as in Section 2 of the test program), so the
CreateProcess() failure described above will occur under Win32 when the
java.home property contains one or more spaces, as it virtually always will.
This means we can't launch activatable services under Win32 unless the 1.4 JRE
is installed to a path structure that does not contain spaces. Unfortunately,
it always gets installed under "C:\Program Files\JavaSoft\JRE\1.4" by default.
We can't reasonably modify java.lang.Runtime or java.lang.Win32Process to fix
the problem either. Any other internal Sun code that uses a similar technique
to launch a JVM (or any other JDK/JRE binary) also presents a problem. Needless
to say, while we can still continue to develop our code, we cannot ship a JDK
1.4 compatible version until this bug is fixed.
The following 3 solutions are recommended (individually or collectively) for
the next release:
1) Restore the code in java.lang.Win32Process to JDK 1.3.1 status. However,
I assume the change was made for some particular reason, perhaps compatibility
with later versions of Windows, I don't know. If the change cannot be reversed,
it should be implemented only for those Windows versions that require it.
java.lang.Runtime.exec(String[]) specifically tested in those instances.
2) Modify java.lang.Runtime to quote command string array elements in their
entirety, if they contain spaces and prior to any tokenizing. Otherwise, move
the tokenizing and integrate in with java.lang.Win32Process. Again, if there is
a platform-specific reason this can't be done, at least do it for Win95/98/NT
prior to Win2K. I can see the argument that the programmer should know that
they need to do this, but on the other hand, you're already helping them out in
a similar fashion in Win32Process.
3) Modify sun.rmi.server.Activation.initCommand() to properly quote the
path in java.home if it contains spaces, at least on the Win32 platforms
mentioned.
In addition, the following change is also recommended:
1) Modify Win32Process_md.c to save the value returned from the GetLastError
() API immediately after calling the CreateProcess() API, so that it can
actually be returned in the generated exception, rather than being overwritten
by the intervening CloseHandle() API call.
Well, I've had my say. What do you folks think?
(Review ID: 132285)
======================================================================
- duplicates
-
JDK-4491217 Runtime.exec of 1.4.0 beta75 does not work on WindowsNT/2000
- Resolved