Review request for 7034570 - java.lang.Runtime.exec(String[] cmd, String[] env) can not work properly if SystemRoot not inherited

Ulf Zibis Ulf.Zibis at gmx.de
Tue Apr 19 12:50:26 UTC 2011


Because the attachments were truncated by mailman, I inline my code below...

Am 18.04.2011 21:42, schrieb Ulf Zibis:
> Am 15.04.2011 15:43, schrieb Michael McMahon:
>> I have incorporated much of Ulf's approach into this version.
>>
>> I agree with Alan about the spec. We could find other variables in the future that need
>> to be set, but can have alternative values other than the default. I think this approach
>> is the most flexible.
>>
>> http://cr.openjdk.java.net/~michaelm/7034570/webrev.4/
>>
>
> Alternatively we could insert the SystemRoot variable from the beginning in the ProcessEnvironment 
> constructor.
> See my approach in the attachment, based on b84 sources and using TreeMap instead HashMap.
> IMO, looks much smarter.
>
> Please note, that my system is in a construction site state and not up to date. So I couldn't test 
> the code.
>
> -Ulf
>

====================================================================================

/*
  * Copyright 2003-2006 Sun Microsystems, Inc.  All rights reserved.
  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  */

/* We use APIs that access a so-called Windows "Environment Block",
  * which looks like an array of jchars like this:
  *
  * FOO=BAR\u0000 ... GORP=QUUX\u0000\u0000
  *
  * This data structure has a number of peculiarities we must contend with:
  * (see: http://windowssdk.msdn.microsoft.com/en-us/library/ms682009.aspx)
  * - The NUL jchar separators, and a double NUL jchar terminator.
  *   It appears that the Windows implementation requires double NUL
  *   termination even if the environment is empty.  We should always
  *   generate environments with double NUL termination, while accepting
  *   empty environments consisting of a single NUL.
  * - on Windows9x, this is actually an array of 8-bit chars, not jchars,
  *   encoded in the system default encoding.
  * - The block must be sorted by Unicode value, case-insensitively,
  *   as if folded to upper case.
  * - There are magic environment variables maintained by Windows
  *   that start with a `=' (!) character.  These are used for
  *   Windows drive current directory (e.g. "=C:=C:\WINNT") or the
  *   exit code of the last command (e.g. "=ExitCode=0000001").
  *
  * Since Java and non-9x Windows speak the same character set, and
  * even the same encoding, we don't have to deal with unreliable
  * conversion to byte streams.  Just add a few NUL terminators.
  *
  * System.getenv(String) is case-insensitive, while System.getenv()
  * returns a map that is case-sensitive, which is consistent with
  * native Windows APIs.
  *
  * 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
  * @author Ulf Zibis
  * @since 1.5
  */

package java.lang;

import java.util.*;

final class ProcessEnvironment extends TreeMap<String,String>
{
     private static String validateName(String name) {
         // An initial `=' indicates a magic Windows variable name -- OK
         if (name.indexOf('=', 1)   != -1 ||
             name.indexOf('\u0000') != -1)
             throw new IllegalArgumentException
                 ("Invalid environment variable name: \"" + name + "\"");
         return name;
     }

     private static String validateValue(String value) {
         if (value.indexOf('\u0000') != -1)
             throw new IllegalArgumentException
                 ("Invalid environment variable value: \"" + value + "\"");
         return value;
     }

     private static String nonNullString(Object o) {
         if (o == null)
             throw new NullPointerException();
         return (String) o;
     }

     public String put(String key, String value) {
         return super.put(validateName(key), validateValue(value));
     }

     public String get(Object key) {
         return super.get(nonNullString(key));
     }

     public boolean containsKey(Object key) {
         return super.containsKey(nonNullString(key));
     }

     public boolean containsValue(Object value) {
         return super.containsValue(nonNullString(value));
     }

     public String remove(Object key) {
         return super.remove(nonNullString(key));
     }

     private static class CheckedEntry
         implements Map.Entry<String,String>
     {
         private final Map.Entry<String,String> e;
         public CheckedEntry(Map.Entry<String,String> e) {this.e = e;}
         public String getKey()   { return e.getKey();}
         public String getValue() { return e.getValue();}
         public String setValue(String value) {
             return e.setValue(validateValue(value));
         }
         public String toString() { return getKey() + "=" + getValue();}
         public boolean equals(Object o) {return e.equals(o);}
         public int hashCode()    {return e.hashCode();}
     }

     private static class CheckedEntrySet
         extends AbstractSet<Map.Entry<String,String>>
     {
         private final Set<Map.Entry<String,String>> s;
         public CheckedEntrySet(Set<Map.Entry<String,String>> s) {this.s = s;}
         public int size()        {return s.size();}
         public boolean isEmpty() {return s.isEmpty();}
         public void clear()      {       s.clear();}
         public Iterator<Map.Entry<String,String>> iterator() {
             return new Iterator<>() {
                 Iterator<Map.Entry<String,String>> i = s.iterator();
                 public boolean hasNext() { return i.hasNext();}
                 public Map.Entry<String,String> next() {
                     return new CheckedEntry(i.next());
                 }
                 public void remove() { i.remove();}
             };
         }
         private static Map.Entry<String,String> checkedEntry (Object o) {
             Map.Entry<String,String> e = (Map.Entry<String,String>) o;
             nonNullString(e.getKey());
             nonNullString(e.getValue());
             return e;
         }
         public boolean contains(Object o) {return s.contains(checkedEntry(o));}
         public boolean remove(Object o)   {return s.remove(checkedEntry(o));}
     }

     private static class CheckedValues extends AbstractCollection<String> {
         private final Collection<String> c;
         public CheckedValues(Collection<String> c) {this.c = c;}
         public int size()                  {return c.size();}
         public boolean isEmpty()           {return c.isEmpty();}
         public void clear()                {       c.clear();}
         public Iterator<String> iterator() {return c.iterator();}
         public boolean contains(Object o)  {return c.contains(nonNullString(o));}
         public boolean remove(Object o)    {return c.remove(nonNullString(o));}
     }

     private static class CheckedKeySet extends AbstractSet<String> {
         private final Set<String> s;
         public CheckedKeySet(Set<String> s) {this.s = s;}
         public int size()                  {return s.size();}
         public boolean isEmpty()           {return s.isEmpty();}
         public void clear()                {       s.clear();}
         public Iterator<String> iterator() {return s.iterator();}
         public boolean contains(Object o)  {return s.contains(nonNullString(o));}
         public boolean remove(Object o)    {return s.remove(nonNullString(o));}
     }

     public Set<String> keySet() {
         return new CheckedKeySet(super.keySet());
     }

     public Collection<String> values() {
         return new CheckedValues(super.values());
     }

     public Set<Map.Entry<String,String>> entrySet() {
         return new CheckedEntrySet(super.entrySet());
     }


     // Allow `=' as first char in name, e.g. =C:=C:\DIR
     static final int MIN_NAME_LENGTH = 1;

     private static final Comparator<String> nameComparator;
     private static final ProcessEnvironment theEnvironment;
     private static final Map<String,String> theUnmodifiableEnvironment;

     static {
         nameComparator  = new Comparator<>() {
             public int compare(String s1, String s2) {
                 // We can't use String.compareToIgnoreCase since it
                 // canonicalizes to lower case, while Windows
                 // canonicalizes to upper case!  For example, "_" should
                 // sort *after* "Z", not before.
                 int n1 = s1.length();
                 int n2 = s2.length();
                 int min = Math.min(n1, n2);
                 for (int i = 0; i < min; i++) {
                     char c1 = s1.charAt(i);
                     char c2 = s2.charAt(i);
                     if (c1 != c2) {
                         c1 = Character.toUpperCase(c1);
                         c2 = Character.toUpperCase(c2);
                         if (c1 != c2)
                             // No overflow because of numeric promotion
                             return c1 - c2;
                     }
                 }
                 return n1 - n2;
             }
         };
         theEnvironment  = new ProcessEnvironment();
         theUnmodifiableEnvironment
             = Collections.unmodifiableMap(theEnvironment);

         String envblock = environmentBlock();
         int beg, end, eql;
         for (beg = 0;
              ((end = envblock.indexOf('\u0000', beg  )) != -1 &&
               // An initial `=' indicates a magic Windows variable name -- OK
               (eql = envblock.indexOf('='     , beg+1)) != -1);
              beg = end + 1) {
             // Ignore corrupted environment strings.
             if (eql < end)
                 theEnvironment.put(envblock.substring(beg, eql),
                                    envblock.substring(eql+1,end));
         }
     }

     private ProcessEnvironment() {
         super(nameComparator);
         // Some versions of MSVCRT.DLL require SystemRoot to be set.
         // So, we make sure that it is always set, even if not provided
         // by the caller.
         final String SYSTEMROOT = "SystemRoot";
         try {
             put(SYSTEMROOT, getenv(SYSTEMROOT));
         } catch (NullPointerException npe) {}
     }

     // Only for use by System.getenv(String)
     static String getenv(String name) {
         // The original implementation used a native call to _wgetenv,
         // but it turns out that _wgetenv is only consistent with
         // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used
         // instead of `main', even in a process created using
         // CREATE_UNICODE_ENVIRONMENT.  Instead we perform the
         // case-insensitive comparison ourselves.  At least this
         // guarantees that System.getenv().get(String) will be
         // consistent with System.getenv(String).
         return theEnvironment.get(name);
     }

     // Only for use by System.getenv()
     static Map<String,String> getenv() {
         return theUnmodifiableEnvironment;
     }

     // Only for use by ProcessBuilder.environment()
     static ProcessEnvironment environment() {
         return (ProcessEnvironment)theEnvironment.clone();
     }

     // Only for use by ProcessBuilder.environment(String[] envp)
     static ProcessEnvironment emptyEnvironment() {
         return new ProcessEnvironment();
     }

     private static native String environmentBlock();

     // Only for use by ProcessImpl.start()
     String toEnvironmentBlock() {
         StringBuilder sb = new StringBuilder(size()*30);
         for (Map.Entry<String,String> e : entrySet())
             sb.append(e.getKey())
               .append('=')
               .append(e.getValue())
               .append('\u0000');
         // Ensure double NUL termination, even if environment is empty.
         if (sb.length() == 0)
             sb.append('\u0000');
         sb.append('\u0000');
         return sb.toString();
     }

     static String toEnvironmentBlock(ProcessEnvironment map) {
         return map == null ? null :
             map.toEnvironmentBlock();
     }
}

====================================================================================

/*
  * Copyright 2003-2008 Sun Microsystems, Inc.  All rights reserved.
  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  */

package java.lang;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;

/**
  * This class is used to create operating system processes.
  *
  * <p>Each {@code ProcessBuilder} instance manages a collection
  * of process attributes.  The {@link #start()} method creates a new
  * {@link Process} instance with those attributes.  The {@link
  * #start()} method can be invoked repeatedly from the same instance
  * to create new subprocesses with identical or related attributes.
  *
  * <p>Each process builder manages these process attributes:
  *
  * <ul>
  *
  * <li>a <i>command</i>, a list of strings which signifies the
  * external program file to be invoked and its arguments, if any.
  * Which string lists represent a valid operating system command is
  * system-dependent.  For example, it is common for each conceptual
  * argument to be an element in this list, but there are operating
  * systems where programs are expected to tokenize command line
  * strings themselves - on such a system a Java implementation might
  * require commands to contain exactly two elements.
  *
  * <li>an <i>environment</i>, which is a system-dependent mapping from
  * <i>variables</i> to <i>values</i>.  The initial value is a copy of
  * the environment of the current process (see {@link System#getenv()}).
  *
  * <li>a <i>working directory</i>.  The default value is the current
  * working directory of the current process, usually the directory
  * named by the system property {@code user.dir}.
  *
  * <li><a name="redirect-input">a source of <i>standard input</i>.
  * By default, the subprocess reads input from a pipe.  Java code
  * can access this pipe via the output stream returned by
  * {@link Process#getOutputStream()}.  However, standard input may
  * be redirected to another source using
  * {@link #redirectInput(Redirect) redirectInput}.
  * In this case, {@link Process#getOutputStream()} will return a
  * <i>null output stream</i>, for which:
  *
  * <ul>
  * <li>the {@link OutputStream#write(int) write} methods always
  * throw {@code IOException}
  * <li>the {@link OutputStream#close() close} method does nothing
  * </ul>
  *
  * <li><a name="redirect-output">a destination for <i>standard output</i>
  * and <i>standard error</i>.  By default, the subprocess writes standard
  * output and standard error to pipes.  Java code can access these pipes
  * via the input streams returned by {@link Process#getInputStream()} and
  * {@link Process#getErrorStream()}.  However, standard output and
  * standard error may be redirected to other destinations using
  * {@link #redirectOutput(Redirect) redirectOutput} and
  * {@link #redirectError(Redirect) redirectError}.
  * In this case, {@link Process#getInputStream()} and/or
  * {@link Process#getErrorStream()} will return a <i>null input
  * stream</i>, for which:
  *
  * <ul>
  * <li>the {@link InputStream#read() read} methods always return
  * {@code -1}
  * <li>the {@link InputStream#available() available} method always returns
  * {@code 0}
  * <li>the {@link InputStream#close() close} method does nothing
  * </ul>
  *
  * <li>a <i>redirectErrorStream</i> property.  Initially, this property
  * is {@code false}, meaning that the standard output and error
  * output of a subprocess are sent to two separate streams, which can
  * be accessed using the {@link Process#getInputStream()} and {@link
  * Process#getErrorStream()} methods.
  *
  * <p>If the value is set to {@code true}, then:
  *
  * <ul>
  * <li>standard error is merged with the standard output and always sent
  * to the same destination (this makes it easier to correlate error
  * messages with the corresponding output)
  * <li>the common destination of standard error and standard output can be
  * redirected using
  * {@link #redirectOutput(Redirect) redirectOutput}
  * <li>any redirection set by the
  * {@link #redirectError(Redirect) redirectError}
  * method is ignored when creating a subprocess
  * <li>the stream returned from {@link Process#getErrorStream()} will
  * always be a <a href="#redirect-output">null input stream</a>
  * </ul>
  *
  * </ul>
  *
  * <p>Modifying a process builder's attributes will affect processes
  * subsequently started by that object's {@link #start()} method, but
  * will never affect previously started processes or the Java process
  * itself.
  *
  * <p>Most error checking is performed by the {@link #start()} method.
  * It is possible to modify the state of an object so that {@link
  * #start()} will fail.  For example, setting the command attribute to
  * an empty list will not throw an exception unless {@link #start()}
  * is invoked.
  *
  * <p><strong>Note that this class is not synchronized.</strong>
  * If multiple threads access a {@code ProcessBuilder} instance
  * concurrently, and at least one of the threads modifies one of the
  * attributes structurally, it <i>must</i> be synchronized externally.
  *
  * <p>Starting a new process which uses the default working directory
  * and environment is easy:
  *
  * <pre> {@code
  * Process p = new ProcessBuilder("myCommand", "myArg").start();
  * }</pre>
  *
  * <p>Here is an example that starts a process with a modified working
  * directory and environment, and redirects standard output and error
  * to be appended to a log file:
  *
  * <pre> {@code
  * ProcessBuilder pb =
  *   new ProcessBuilder("myCommand", "myArg1", "myArg2");
  * Map<String, String> env = pb.environment();
  * env.put("VAR1", "myValue");
  * env.remove("OTHERVAR");
  * env.put("VAR2", env.get("VAR1") + "suffix");
  * pb.directory(new File("myDir"));
  * File log = new File("log");
  * pb.redirectErrorStream(true);
  * pb.redirectOutput(Redirect.appendTo(log));
  * Process p = pb.start();
  * assert pb.redirectInput() == Redirect.PIPE;
  * assert pb.redirectOutput().file() == log;
  * assert p.getInputStream().read() == -1;
  * }</pre>
  *
  * <p>To start a process with an explicit set of environment
  * variables, first call {@link java.util.Map#clear() Map.clear()}
  * before adding environment variables.
  *
  * @author Martin Buchholz
  * @since 1.5
  */

public final class ProcessBuilder
{
     private List<String> command;
     private File directory;
     private ProcessEnvironment environment;
     private boolean redirectErrorStream;
     private Redirect[] redirects;

     /**
      * Constructs a process builder with the specified operating
      * system program and arguments.  This constructor does <i>not</i>
      * make a copy of the {@code command} list.  Subsequent
      * updates to the list will be reflected in the state of the
      * process builder.  It is not checked whether
      * {@code command} corresponds to a valid operating system
      * command.
      *
      * @param  command the list containing the program and its arguments
      * @throws NullPointerException if the argument is null
      */
     public ProcessBuilder(List<String> command) {
         command(command);
     }

     /**
      * Constructs a process builder with the specified operating
      * system program and arguments.  This is a convenience
      * constructor that sets the process builder's command to a string
      * list containing the same strings as the {@code command}
      * array, in the same order.  It is not checked whether
      * {@code command} corresponds to a valid operating system
      * command.
      *
      * @param command a string array containing the program and its arguments
      */
     public ProcessBuilder(String... command) {
         command(command);
     }

     /**
      * Sets this process builder's operating system program and
      * arguments.  This method does <i>not</i> make a copy of the
      * {@code command} list.  Subsequent updates to the list will
      * be reflected in the state of the process builder.  It is not
      * checked whether {@code command} corresponds to a valid
      * operating system command.
      *
      * @param  command the list containing the program and its arguments
      * @return this process builder
      *
      * @throws NullPointerException if the argument is null
      */
     public ProcessBuilder command(List<String> command) {
         if (command == null)
             throw new NullPointerException();
         this.command = command;
         return this;
     }

     /**
      * Sets this process builder's operating system program and
      * arguments.  This is a convenience method that sets the command
      * to a string list containing the same strings as the
      * {@code command} array, in the same order.  It is not
      * checked whether {@code command} corresponds to a valid
      * operating system command.
      *
      * @param  command a string array containing the program and its arguments
      * @return this process builder
      */
     public ProcessBuilder command(String... command) {
         this.command = new ArrayList<String>(command.length);
         for (String arg : command)
             this.command.add(arg);
         return this;
     }

     /**
      * Returns this process builder's operating system program and
      * arguments.  The returned list is <i>not</i> a copy.  Subsequent
      * updates to the list will be reflected in the state of this
      * process builder.
      *
      * @return this process builder's program and its arguments
      */
     public List<String> command() {
         return command;
     }

     /**
      * Returns a string map view of this process builder's environment.
      *
      * Whenever a process builder is created, the environment is
      * initialized to a copy of the current process environment (see
      * {@link System#getenv()}).  Subprocesses subsequently started by
      * this object's {@link #start()} method will use this map as
      * their environment.
      *
      * <p>The returned object may be modified using ordinary {@link
      * java.util.Map Map} operations.  These modifications will be
      * visible to subprocesses started via the {@link #start()}
      * method.  Two {@code ProcessBuilder} instances always
      * contain independent process environments, so changes to the
      * returned map will never be reflected in any other
      * {@code ProcessBuilder} instance or the values returned by
      * {@link System#getenv System.getenv}.
      *
      * <p>If the system does not support environment variables, an
      * empty map is returned.
      *
      * <p>The returned map does not permit null keys or values.
      * Attempting to insert or query the presence of a null key or
      * value will throw a {@link NullPointerException}.
      * Attempting to query the presence of a key or value which is not
      * of type {@link String} will throw a {@link ClassCastException}.
      *
      * <p>The behavior of the returned map is system-dependent.  A
      * system may not allow modifications to environment variables or
      * may forbid certain variable names or values.  For this reason,
      * attempts to modify the map may fail with
      * {@link UnsupportedOperationException} or
      * {@link IllegalArgumentException}
      * if the modification is not permitted by the operating system.
      *
      * <p>Since the external format of environment variable names and
      * values is system-dependent, there may not be a one-to-one
      * mapping between them and Java's Unicode strings.  Nevertheless,
      * the map is implemented in such a way that environment variables
      * which are not modified by Java code will have an unmodified
      * native representation in the subprocess.
      *
      * <p>The returned map and its collection views may not obey the
      * general contract of the {@link Object#equals} and
      * {@link Object#hashCode} methods.
      *
      * <p>The returned map is typically case-sensitive on all platforms.
      *
      * <p>If a security manager exists, its
      * {@link SecurityManager#checkPermission checkPermission} method
      * is called with a
      * {@link RuntimePermission}{@code ("getenv.*")} permission.
      * This may result in a {@link SecurityException} being thrown.
      *
      * <p>When passing information to a Java subprocess,
      * <a href=System.html#EnvironmentVSSystemProperties>system properties</a>
      * are generally preferred over environment variables.
      *
      * @return this process builder's environment
      *
      * @throws SecurityException
      *         if a security manager exists and its
      *         {@link SecurityManager#checkPermission checkPermission}
      *         method doesn't allow access to the process environment
      *
      * @see    Runtime#exec(String[],String[],java.io.File)
      * @see    System#getenv()
      */
     public ProcessEnvironment environment() {
         SecurityManager security = System.getSecurityManager();
         if (security != null)
             security.checkPermission(new RuntimePermission("getenv.*"));

         if (environment == null)
             environment = ProcessEnvironment.environment();

         assert environment != null;

         return environment;
     }

     // Only for use by Runtime.exec(...envp...)
     ProcessBuilder environment(String[] envp) {
         assert environment == null;
         if (envp != null) {
             environment = ProcessEnvironment.emptyEnvironment();
             assert environment != null;

             for (String envstring : envp) {
                 // Before 1.5, we blindly passed invalid envstrings
                 // to the child process.
                 // We would like to throw an exception, but do not,
                 // for compatibility with old broken code.

                 // Silently discard any trailing junk.
                 if (envstring.indexOf((int) '\u0000') != -1)
                     envstring = envstring.replaceFirst("\u0000.*", "");

                 int eqlsign =
                     envstring.indexOf('=', ProcessEnvironment.MIN_NAME_LENGTH);
                 // Silently ignore envstrings lacking the required `='.
                 if (eqlsign != -1)
                     environment.put(envstring.substring(0,eqlsign),
                                     envstring.substring(eqlsign+1));
             }
         }
         return this;
     }

     /**
      * Returns this process builder's working directory.
      *
      * Subprocesses subsequently started by this object's {@link
      * #start()} method will use this as their working directory.
      * The returned value may be {@code null} -- this means to use
      * the working directory of the current Java process, usually the
      * directory named by the system property {@code user.dir},
      * as the working directory of the child process.
      *
      * @return this process builder's working directory
      */
     public File directory() {
         return directory;
     }

     /**
      * Sets this process builder's working directory.
      *
      * Subprocesses subsequently started by this object's {@link
      * #start()} method will use this as their working directory.
      * The argument may be {@code null} -- this means to use the
      * working directory of the current Java process, usually the
      * directory named by the system property {@code user.dir},
      * as the working directory of the child process.
      *
      * @param  directory the new working directory
      * @return this process builder
      */
     public ProcessBuilder directory(File directory) {
         this.directory = directory;
         return this;
     }

     // ---------------- I/O Redirection ----------------

     /**
      * Implements a <a href="#redirect-output">null input stream</a>.
      */
     static class NullInputStream extends InputStream {
         public int read()      { return -1; }
         public int available() { return 0; }
     }

     /**
      * Implements a <a href="#redirect-input">null output stream</a>.
      */
     static class NullOutputStream extends OutputStream {
         public void write(int b) throws IOException {
             throw new IOException("Stream closed");
         }
     }

     /**
      * Represents a source of subprocess input or a destination of
      * subprocess output.
      *
      * Each {@code Redirect} instance is one of the following:
      *
      * <ul>
      * <li>the special value {@link #PIPE Redirect.PIPE}
      * <li>the special value {@link #INHERIT Redirect.INHERIT}
      * <li>a redirection to read from a file, created by an invocation of
      *     {@link Redirect#from Redirect.from(File)}
      * <li>a redirection to write to a file,  created by an invocation of
      *     {@link Redirect#to Redirect.to(File)}
      * <li>a redirection to append to a file, created by an invocation of
      *     {@link Redirect#appendTo Redirect.appendTo(File)}
      * </ul>
      *
      * <p>Each of the above categories has an associated unique
      * {@link Type Type}.
      *
      * @since 1.7
      */
     public static abstract class Redirect {
         /**
          * The type of a {@link Redirect}.
          */
         public enum Type {
             /**
              * The type of {@link Redirect#PIPE Redirect.PIPE}.
              */
             PIPE,

             /**
              * The type of {@link Redirect#INHERIT Redirect.INHERIT}.
              */
             INHERIT,

             /**
              * The type of redirects returned from
              * {@link Redirect#from Redirect.from(File)}.
              */
             READ,

             /**
              * The type of redirects returned from
              * {@link Redirect#to Redirect.to(File)}.
              */
             WRITE,

             /**
              * The type of redirects returned from
              * {@link Redirect#appendTo Redirect.appendTo(File)}.
              */
             APPEND
         };

         /**
          * Returns the type of this {@code Redirect}.
          * @return the type of this {@code Redirect}
          */
         public abstract Type type();

         /**
          * Indicates that subprocess I/O will be connected to the
          * current Java process over a pipe.
          *
          * This is the default handling of subprocess standard I/O.
          *
          * <p>It will always be true that
          * <pre> {@code
          * Redirect.PIPE.file() == null &&
          * Redirect.PIPE.type() == Redirect.Type.PIPE
          * }</pre>
          */
         public static final Redirect PIPE = new Redirect() {
                 public Type type() { return Type.PIPE; }
                 public String toString() { return type().toString(); }};

         /**
          * Indicates that subprocess I/O source or destination will be the
          * same as those of the current process.  This is the normal
          * behavior of most operating system command interpreters (shells).
          *
          * <p>It will always be true that
          * <pre> {@code
          * Redirect.INHERIT.file() == null &&
          * Redirect.INHERIT.type() == Redirect.Type.INHERIT
          * }</pre>
          */
         public static final Redirect INHERIT = new Redirect() {
                 public Type type() { return Type.INHERIT; }
                 public String toString() { return type().toString(); }};

         /**
          * Returns the {@link File} source or destination associated
          * with this redirect, or {@code null} if there is no such file.
          *
          * @return the file associated with this redirect,
          *         or {@code null} if there is no such file
          */
         public File file() { return null; }

         FileOutputStream toFileOutputStream() throws IOException {
             throw new UnsupportedOperationException();
         }

         /**
          * Returns a redirect to read from the specified file.
          *
          * <p>It will always be true that
          * <pre> {@code
          * Redirect.from(file).file() == file &&
          * Redirect.from(file).type() == Redirect.Type.READ
          * }</pre>
          *
          * @throws NullPointerException if the specified file is null
          * @return a redirect to read from the specified file
          */
         public static Redirect from(final File file) {
             if (file == null)
                 throw new NullPointerException();
             return new Redirect() {
                     public Type type() { return Type.READ; }
                     public File file() { return file; }
                     public String toString() {
                         return "redirect to read from file \"" + file + "\"";
                     }
                 };
         }

         /**
          * Returns a redirect to write to the specified file.
          * If the specified file exists when the subprocess is started,
          * its previous contents will be discarded.
          *
          * <p>It will always be true that
          * <pre> {@code
          * Redirect.to(file).file() == file &&
          * Redirect.to(file).type() == Redirect.Type.WRITE
          * }</pre>
          *
          * @throws NullPointerException if the specified file is null
          * @return a redirect to write to the specified file
          */
         public static Redirect to(final File file) {
             if (file == null)
                 throw new NullPointerException();
             return new Redirect() {
                     public Type type() { return Type.WRITE; }
                     public File file() { return file; }
                     public String toString() {
                         return "redirect to write to file \"" + file + "\"";
                     }
                     FileOutputStream toFileOutputStream() throws IOException {
                         return new FileOutputStream(file, false);
                     }
                 };
         }

         /**
          * Returns a redirect to append to the specified file.
          * Each write operation first advances the position to the
          * end of the file and then writes the requested data.
          * Whether the advancement of the position and the writing
          * of the data are done in a single atomic operation is
          * system-dependent and therefore unspecified.
          *
          * <p>It will always be true that
          * <pre> {@code
          * Redirect.appendTo(file).file() == file &&
          * Redirect.appendTo(file).type() == Redirect.Type.APPEND
          * }</pre>
          *
          * @throws NullPointerException if the specified file is null
          * @return a redirect to append to the specified file
          */
         public static Redirect appendTo(final File file) {
             if (file == null)
                 throw new NullPointerException();
             return new Redirect() {
                     public Type type() { return Type.APPEND; }
                     public File file() { return file; }
                     public String toString() {
                         return "redirect to append to file \"" + file + "\"";
                     }
                     FileOutputStream toFileOutputStream() throws IOException {
                         return new FileOutputStream(file, true);
                     }
                 };
         }

         /**
          * Compares the specified object with this {@code Redirect} for
          * equality.  Returns {@code true} if and only if the two
          * objects are identical or both objects are {@code Redirect}
          * instances of the same type associated with non-null equal
          * {@code File} instances.
          */
         public boolean equals(Object obj) {
             if (obj == this)
                 return true;
             if (! (obj instanceof Redirect))
                 return false;
             Redirect r = (Redirect) obj;
             if (r.type() != this.type())
                 return false;
             assert this.file() != null;
             return this.file().equals(r.file());
         }

         /**
          * Returns a hash code value for this {@code Redirect}.
          * @return a hash code value for this {@code Redirect}
          */
         public int hashCode() {
             File file = file();
             if (file == null)
                 return super.hashCode();
             else
                 return file.hashCode();
         }

         /**
          * No public constructors.  Clients must use predefined
          * static {@code Redirect} instances or factory methods.
          */
         private Redirect() {}
     }

     private Redirect[] redirects() {
         if (redirects == null)
             redirects = new Redirect[] {
                 Redirect.PIPE, Redirect.PIPE, Redirect.PIPE
             };
         return redirects;
     }

     /**
      * Sets this process builder's standard input source.
      *
      * Subprocesses subsequently started by this object's {@link #start()}
      * method obtain their standard input from this source.
      *
      * <p>If the source is {@link Redirect#PIPE Redirect.PIPE}
      * (the initial value), then the standard input of a
      * subprocess can be written to using the output stream
      * returned by {@link Process#getOutputStream()}.
      * If the source is set to any other value, then
      * {@link Process#getOutputStream()} will return a
      * <a href="#redirect-input">null output stream</a>.
      *
      * @param  source the new standard input source
      * @return this process builder
      * @throws IllegalArgumentException
      *         if the redirect does not correspond to a valid source
      *         of data, that is, has type
      *         {@link Redirect.Type#WRITE WRITE} or
      *         {@link Redirect.Type#APPEND APPEND}
      * @since  1.7
      */
     public ProcessBuilder redirectInput(Redirect source) {
         if (source.type() == Redirect.Type.WRITE ||
             source.type() == Redirect.Type.APPEND)
             throw new IllegalArgumentException(
                 "Redirect invalid for reading: " + source);
         redirects()[0] = source;
         return this;
     }

     /**
      * Sets this process builder's standard output destination.
      *
      * Subprocesses subsequently started by this object's {@link #start()}
      * method send their standard output to this destination.
      *
      * <p>If the destination is {@link Redirect#PIPE Redirect.PIPE}
      * (the initial value), then the standard output of a subprocess
      * can be read using the input stream returned by {@link
      * Process#getInputStream()}.
      * If the destination is set to any other value, then
      * {@link Process#getInputStream()} will return a
      * <a href="#redirect-output">null input stream</a>.
      *
      * @param  destination the new standard output destination
      * @return this process builder
      * @throws IllegalArgumentException
      *         if the redirect does not correspond to a valid
      *         destination of data, that is, has type
      *         {@link Redirect.Type#READ READ}
      * @since  1.7
      */
     public ProcessBuilder redirectOutput(Redirect destination) {
         if (destination.type() == Redirect.Type.READ)
             throw new IllegalArgumentException(
                 "Redirect invalid for writing: " + destination);
         redirects()[1] = destination;
         return this;
     }

     /**
      * Sets this process builder's standard error destination.
      *
      * Subprocesses subsequently started by this object's {@link #start()}
      * method send their standard error to this destination.
      *
      * <p>If the destination is {@link Redirect#PIPE Redirect.PIPE}
      * (the initial value), then the error output of a subprocess
      * can be read using the input stream returned by {@link
      * Process#getErrorStream()}.
      * If the destination is set to any other value, then
      * {@link Process#getErrorStream()} will return a
      * <a href="#redirect-output">null input stream</a>.
      *
      * <p>If the {@link #redirectErrorStream redirectErrorStream}
      * attribute has been set {@code true}, then the redirection set
      * by this method has no effect.
      *
      * @param  destination the new standard error destination
      * @return this process builder
      * @throws IllegalArgumentException
      *         if the redirect does not correspond to a valid
      *         destination of data, that is, has type
      *         {@link Redirect.Type#READ READ}
      * @since  1.7
      */
     public ProcessBuilder redirectError(Redirect destination) {
         if (destination.type() == Redirect.Type.READ)
             throw new IllegalArgumentException(
                 "Redirect invalid for writing: " + destination);
         redirects()[2] = destination;
         return this;
     }

     /**
      * Sets this process builder's standard input source to a file.
      *
      * <p>This is a convenience method.  An invocation of the form
      * {@code redirectInput(file)}
      * behaves in exactly the same way as the invocation
      * {@link #redirectInput(Redirect) redirectInput}
      * {@code (Redirect.from(file))}.
      *
      * @param  file the new standard input source
      * @return this process builder
      * @since  1.7
      */
     public ProcessBuilder redirectInput(File file) {
         return redirectInput(Redirect.from(file));
     }

     /**
      * Sets this process builder's standard output destination to a file.
      *
      * <p>This is a convenience method.  An invocation of the form
      * {@code redirectOutput(file)}
      * behaves in exactly the same way as the invocation
      * {@link #redirectOutput(Redirect) redirectOutput}
      * {@code (Redirect.to(file))}.
      *
      * @param  file the new standard output destination
      * @return this process builder
      * @since  1.7
      */
     public ProcessBuilder redirectOutput(File file) {
         return redirectOutput(Redirect.to(file));
     }

     /**
      * Sets this process builder's standard error destination to a file.
      *
      * <p>This is a convenience method.  An invocation of the form
      * {@code redirectError(file)}
      * behaves in exactly the same way as the invocation
      * {@link #redirectError(Redirect) redirectError}
      * {@code (Redirect.to(file))}.
      *
      * @param  file the new standard error destination
      * @return this process builder
      * @since  1.7
      */
     public ProcessBuilder redirectError(File file) {
         return redirectError(Redirect.to(file));
     }

     /**
      * Returns this process builder's standard input source.
      *
      * Subprocesses subsequently started by this object's {@link #start()}
      * method obtain their standard input from this source.
      * The initial value is {@link Redirect#PIPE Redirect.PIPE}.
      *
      * @return this process builder's standard input source
      * @since  1.7
      */
     public Redirect redirectInput() {
         return (redirects == null) ? Redirect.PIPE : redirects[0];
     }

     /**
      * Returns this process builder's standard output destination.
      *
      * Subprocesses subsequently started by this object's {@link #start()}
      * method redirect their standard output to this destination.
      * The initial value is {@link Redirect#PIPE Redirect.PIPE}.
      *
      * @return this process builder's standard output destination
      * @since  1.7
      */
     public Redirect redirectOutput() {
         return (redirects == null) ? Redirect.PIPE : redirects[1];
     }

     /**
      * Returns this process builder's standard error destination.
      *
      * Subprocesses subsequently started by this object's {@link #start()}
      * method redirect their standard error to this destination.
      * The initial value is {@link Redirect#PIPE Redirect.PIPE}.
      *
      * @return this process builder's standard error destination
      * @since  1.7
      */
     public Redirect redirectError() {
         return (redirects == null) ? Redirect.PIPE : redirects[2];
     }

     /**
      * Sets the source and destination for subprocess standard I/O
      * to be the same as those of the current Java process.
      *
      * <p>This is a convenience method.  An invocation of the form
      * <pre> {@code
      * pb.inheritIO()
      * }</pre>
      * behaves in exactly the same way as the invocation
      * <pre> {@code
      * pb.redirectInput(Redirect.INHERIT)
      *   .redirectOutput(Redirect.INHERIT)
      *   .redirectError(Redirect.INHERIT)
      * }</pre>
      *
      * This gives behavior equivalent to most operating system
      * command interpreters, or the standard C library function
      * {@code system()}.
      *
      * @return this process builder
      * @since  1.7
      */
     public ProcessBuilder inheritIO() {
         Arrays.fill(redirects(), Redirect.INHERIT);
         return this;
     }

     /**
      * Tells whether this process builder merges standard error and
      * standard output.
      *
      * <p>If this property is {@code true}, then any error output
      * generated by subprocesses subsequently started by this object's
      * {@link #start()} method will be merged with the standard
      * output, so that both can be read using the
      * {@link Process#getInputStream()} method.  This makes it easier
      * to correlate error messages with the corresponding output.
      * The initial value is {@code false}.
      *
      * @return this process builder's {@code redirectErrorStream} property
      */
     public boolean redirectErrorStream() {
         return redirectErrorStream;
     }

     /**
      * Sets this process builder's {@code redirectErrorStream} property.
      *
      * <p>If this property is {@code true}, then any error output
      * generated by subprocesses subsequently started by this object's
      * {@link #start()} method will be merged with the standard
      * output, so that both can be read using the
      * {@link Process#getInputStream()} method.  This makes it easier
      * to correlate error messages with the corresponding output.
      * The initial value is {@code false}.
      *
      * @param  redirectErrorStream the new property value
      * @return this process builder
      */
     public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) {
         this.redirectErrorStream = redirectErrorStream;
         return this;
     }

     /**
      * Starts a new process using the attributes of this process builder.
      *
      * <p>The new process will
      * invoke the command and arguments given by {@link #command()},
      * in a working directory as given by {@link #directory()},
      * with a process environment as given by {@link #environment()}.
      *
      * <p>This method checks that the command is a valid operating
      * system command.  Which commands are valid is system-dependent,
      * but at the very least the command must be a non-empty list of
      * non-null strings.
      *
      * <p>If there is a security manager, its
      * {@link SecurityManager#checkExec checkExec}
      * method is called with the first component of this object's
      * {@code command} array as its argument. This may result in
      * a {@link SecurityException} being thrown.
      *
      * <p>Starting an operating system process is highly system-dependent.
      * Among the many things that can go wrong are:
      * <ul>
      * <li>The operating system program file was not found.
      * <li>Access to the program file was denied.
      * <li>The working directory does not exist.
      * </ul>
      *
      * <p>In such cases an exception will be thrown.  The exact nature
      * of the exception is system-dependent, but it will always be a
      * subclass of {@link IOException}.
      *
      * <p>Subsequent modifications to this process builder will not
      * affect the returned {@link Process}.
      *
      * @return a new {@link Process} object for managing the subprocess
      *
      * @throws NullPointerException
      *         if an element of the command list is null
      *
      * @throws IndexOutOfBoundsException
      *         if the command is an empty list (has size {@code 0})
      *
      * @throws SecurityException
      *         if a security manager exists and
      * <ul>
      *
      * <li>its
      *         {@link SecurityManager#checkExec checkExec}
      *         method doesn't allow creation of the subprocess, or
      *
      * <li>the standard input to the subprocess was
      *         {@linkplain #redirectInput redirected from a file}
      *         and the security manager's
      *         {@link SecurityManager#checkRead checkRead} method
      *         denies read access to the file, or
      *
      * <li>the standard output or standard error of the
      *         subprocess was
      *         {@linkplain #redirectOutput redirected to a file}
      *         and the security manager's
      *         {@link SecurityManager#checkWrite checkWrite} method
      *         denies write access to the file
      *
      * </ul>
      *
      * @throws IOException if an I/O error occurs
      *
      * @see Runtime#exec(String[], String[], java.io.File)
      */
     public Process start() throws IOException {
         // Must convert to array first -- a malicious user-supplied
         // list might try to circumvent the security check.
         String[] cmdarray = command.toArray(new String[command.size()]);
         for (String arg : cmdarray)
             if (arg == null)
                 throw new NullPointerException();
         // Throws IndexOutOfBoundsException if command is empty
         String prog = cmdarray[0];

         SecurityManager security = System.getSecurityManager();
         if (security != null)
             security.checkExec(prog);

         String dir = directory == null ? null : directory.toString();

         try {
             return ProcessImpl.start(cmdarray,
                                      environment,
                                      dir,
                                      redirects,
                                      redirectErrorStream);
         } catch (IOException e) {
             // It's much easier for us to create a high-quality error
             // message than the low-level C code which found the problem.
             throw new IOException(
                 "Cannot run program \"" + prog + "\""
                 + (dir == null ? "" : " (in directory \"" + dir + "\")")
                 + ": " + e.getMessage(),
                 e);
         }
     }
}

====================================================================================

/*
  * Copyright 1995-2008 Sun Microsystems, Inc.  All rights reserved.
  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  *
  */

package java.lang;

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
  * create new processes.
  *
  * @author Martin Buchholz
  * @since   1.5
  */

final class ProcessImpl extends Process {
     private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
         = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();

     // System-dependent portion of ProcessBuilder.start()
     static Process start(String cmdarray[],
                          ProcessEnvironment environment,
                          String dir,
                          ProcessBuilder.Redirect[] redirects,
                          boolean redirectErrorStream)
         throws IOException
     {
         String envblock = ProcessEnvironment.toEnvironmentBlock(environment);

         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 OutputStream stdin_stream;
     private InputStream stdout_stream;
     private InputStream stderr_stream;

     private ProcessImpl(final String cmd[],
                         final String envblock,
                         final String path,
                         final long[] stdHandles,
                         final boolean redirectErrorStream)
         throws IOException
     {
         // Win32 CreateProcess requires cmd[0] to be normalized
         cmd[0] = new File(cmd[0]).getPath();

         StringBuilder cmdbuf = new StringBuilder(80);
         for (int i = 0; i < cmd.length; i++) {
             if (i > 0) {
                 cmdbuf.append(' ');
             }
             String s = cmd[i];
             if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) {
                 if (s.charAt(0) != '"') {
                     cmdbuf.append('"');
                     cmdbuf.append(s);
                     if (s.endsWith("\\")) {
                         cmdbuf.append("\\");
                     }
                     cmdbuf.append('"');
                 } else if (s.endsWith("\"")) {
                     /* The argument has already been quoted. */
                     cmdbuf.append(s);
                 } else {
                     /* Unmatched quote for the argument. */
                     throw new IllegalArgumentException();
                 }
             } else {
                 cmdbuf.append(s);
             }
         }
         String cmdstr = cmdbuf.toString();

         handle = create(cmdstr, envblock, path,
                         stdHandles, redirectErrorStream);

         java.security.AccessController.doPrivileged(
         new java.security.PrivilegedAction<Void>() {
         public Void run() {
             if (stdHandles[0] == -1L)
                 stdin_stream = new ProcessBuilder.NullOutputStream();
             else {
                 FileDescriptor stdin_fd = new FileDescriptor();
                 fdAccess.setHandle(stdin_fd, stdHandles[0]);
                 stdin_stream = new BufferedOutputStream(
                     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() {
         return stdin_stream;
     }

     public InputStream getInputStream() {
         return stdout_stream;
     }

     public InputStream getErrorStream() {
         return stderr_stream;
     }

     public void finalize() {
         closeHandle(handle);
     }

     private static final int STILL_ACTIVE = getStillActive();
     private static native int getStillActive();

     public int exitValue() {
         int exitCode = getExitCodeProcess(handle);
         if (exitCode == STILL_ACTIVE)
             throw new IllegalThreadStateException("process has not exited");
         return exitCode;
     }
     private static native int getExitCodeProcess(long handle);

     public int waitFor() throws InterruptedException {
         waitForInterruptibly(handle);
         if (Thread.interrupted())
             throw new InterruptedException();
         return exitValue();
     }
     private static native void waitForInterruptibly(long handle);

     public void destroy() { terminateProcess(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,
                                       String envblock,
                                       String dir,
                                       long[] stdHandles,
                                       boolean redirectErrorStream)
         throws IOException;

     private static native boolean closeHandle(long handle);
}

====================================================================================





More information about the core-libs-dev mailing list