1 /*
   2  * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689
  27  *      5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313
  28  *      6464154 6523983 6206031 4960438 6631352 6631966 6850957 6850958
  29  *      4947220 7018606 7034570
  30  * @summary Basic tests for Process and Environment Variable code
  31  * @run main/othervm/timeout=300 Basic
  32  * @run main/othervm/timeout=300 -Djdk.lang.Process.launchMechanism=fork Basic
  33  * @author Martin Buchholz
  34  */
  35 
  36 import java.lang.ProcessBuilder.Redirect;
  37 import static java.lang.ProcessBuilder.Redirect.*;
  38 
  39 import java.io.*;
  40 import java.lang.reflect.Field;
  41 import java.util.*;
  42 import java.util.concurrent.CountDownLatch;
  43 import java.security.*;
  44 import sun.misc.Unsafe;
  45 import java.util.regex.Pattern;
  46 import java.util.regex.Matcher;
  47 import static java.lang.System.getenv;
  48 import static java.lang.System.out;
  49 import static java.lang.Boolean.TRUE;
  50 import static java.util.AbstractMap.SimpleImmutableEntry;
  51 
  52 public class Basic {
  53 
  54     /* used for Windows only */
  55     static final String systemRoot = System.getenv("SystemRoot");
  56 
  57     /* used for Mac OS X only */
  58     static final String cfUserTextEncoding = System.getenv("__CF_USER_TEXT_ENCODING");
  59 
  60     private static String commandOutput(Reader r) throws Throwable {
  61         StringBuilder sb = new StringBuilder();
  62         int c;
  63         while ((c = r.read()) > 0)
  64             if (c != '\r')
  65                 sb.append((char) c);
  66         return sb.toString();
  67     }
  68 
  69     private static String commandOutput(Process p) throws Throwable {
  70         check(p.getInputStream()  == p.getInputStream());
  71         check(p.getOutputStream() == p.getOutputStream());
  72         check(p.getErrorStream()  == p.getErrorStream());
  73         Reader r = new InputStreamReader(p.getInputStream(),"UTF-8");
  74         String output = commandOutput(r);
  75         equal(p.waitFor(), 0);
  76         equal(p.exitValue(), 0);
  77         return output;
  78     }
  79 
  80     private static String commandOutput(ProcessBuilder pb) {
  81         try {
  82             return commandOutput(pb.start());
  83         } catch (Throwable t) {
  84             String commandline = "";
  85             for (String arg : pb.command())
  86                 commandline += " " + arg;
  87             System.out.println("Exception trying to run process: " + commandline);
  88             unexpected(t);
  89             return "";
  90         }
  91     }
  92 
  93     private static String commandOutput(String...command) {
  94         try {
  95             return commandOutput(Runtime.getRuntime().exec(command));
  96         } catch (Throwable t) {
  97             String commandline = "";
  98             for (String arg : command)
  99                 commandline += " " + arg;
 100             System.out.println("Exception trying to run process: " + commandline);
 101             unexpected(t);
 102             return "";
 103         }
 104     }
 105 
 106     private static void checkCommandOutput(ProcessBuilder pb,
 107                                            String expected,
 108                                            String failureMsg) {
 109         String got = commandOutput(pb);
 110         check(got.equals(expected),
 111               failureMsg + "\n" +
 112               "Expected: \"" + expected + "\"\n" +
 113               "Got: \"" + got + "\"");
 114     }
 115 
 116     private static String absolutifyPath(String path) {
 117         StringBuilder sb = new StringBuilder();
 118         for (String file : path.split(File.pathSeparator)) {
 119             if (sb.length() != 0)
 120                 sb.append(File.pathSeparator);
 121             sb.append(new File(file).getAbsolutePath());
 122         }
 123         return sb.toString();
 124     }
 125 
 126     // compare windows-style, by canonicalizing to upper case,
 127     // not lower case as String.compareToIgnoreCase does
 128     private static class WindowsComparator
 129         implements Comparator<String> {
 130         public int compare(String x, String y) {
 131             return x.toUpperCase(Locale.US)
 132                 .compareTo(y.toUpperCase(Locale.US));
 133         }
 134     }
 135 
 136     private static String sortedLines(String lines) {
 137         String[] arr = lines.split("\n");
 138         List<String> ls = new ArrayList<String>();
 139         for (String s : arr)
 140             ls.add(s);
 141         Collections.sort(ls, new WindowsComparator());
 142         StringBuilder sb = new StringBuilder();
 143         for (String s : ls)
 144             sb.append(s + "\n");
 145         return sb.toString();
 146     }
 147 
 148     private static void compareLinesIgnoreCase(String lines1, String lines2) {
 149         if (! (sortedLines(lines1).equalsIgnoreCase(sortedLines(lines2)))) {
 150             String dashes =
 151                 "-----------------------------------------------------";
 152             out.println(dashes);
 153             out.print(sortedLines(lines1));
 154             out.println(dashes);
 155             out.print(sortedLines(lines2));
 156             out.println(dashes);
 157             out.println("sizes: " + sortedLines(lines1).length() +
 158                         " " + sortedLines(lines2).length());
 159 
 160             fail("Sorted string contents differ");
 161         }
 162     }
 163 
 164     private static final Runtime runtime = Runtime.getRuntime();
 165 
 166     private static final String[] winEnvCommand = {"cmd.exe", "/c", "set"};
 167 
 168     private static String winEnvFilter(String env) {
 169         return env.replaceAll("\r", "")
 170             .replaceAll("(?m)^(?:COMSPEC|PROMPT|PATHEXT)=.*\n","");
 171     }
 172 
 173     private static String unixEnvProg() {
 174         return new File("/usr/bin/env").canExecute() ? "/usr/bin/env"
 175             : "/bin/env";
 176     }
 177 
 178     private static String nativeEnv(String[] env) {
 179         try {
 180             if (Windows.is()) {
 181                 return winEnvFilter
 182                     (commandOutput(runtime.exec(winEnvCommand, env)));
 183             } else {
 184                 return commandOutput(runtime.exec(unixEnvProg(), env));
 185             }
 186         } catch (Throwable t) { throw new Error(t); }
 187     }
 188 
 189     private static String nativeEnv(ProcessBuilder pb) {
 190         try {
 191             if (Windows.is()) {
 192                 pb.command(winEnvCommand);
 193                 return winEnvFilter(commandOutput(pb));
 194             } else {
 195                 pb.command(new String[]{unixEnvProg()});
 196                 return commandOutput(pb);
 197             }
 198         } catch (Throwable t) { throw new Error(t); }
 199     }
 200 
 201     private static void checkSizes(Map<String,String> environ, int size) {
 202         try {
 203             equal(size, environ.size());
 204             equal(size, environ.entrySet().size());
 205             equal(size, environ.keySet().size());
 206             equal(size, environ.values().size());
 207 
 208             boolean isEmpty = (size == 0);
 209             equal(isEmpty, environ.isEmpty());
 210             equal(isEmpty, environ.entrySet().isEmpty());
 211             equal(isEmpty, environ.keySet().isEmpty());
 212             equal(isEmpty, environ.values().isEmpty());
 213         } catch (Throwable t) { unexpected(t); }
 214     }
 215 
 216     private interface EnvironmentFrobber {
 217         void doIt(Map<String,String> environ);
 218     }
 219 
 220     private static void testVariableDeleter(EnvironmentFrobber fooDeleter) {
 221         try {
 222             Map<String,String> environ = new ProcessBuilder().environment();
 223             environ.put("Foo", "BAAR");
 224             fooDeleter.doIt(environ);
 225             equal(environ.get("Foo"), null);
 226             equal(environ.remove("Foo"), null);
 227         } catch (Throwable t) { unexpected(t); }
 228     }
 229 
 230     private static void testVariableAdder(EnvironmentFrobber fooAdder) {
 231         try {
 232             Map<String,String> environ = new ProcessBuilder().environment();
 233             environ.remove("Foo");
 234             fooAdder.doIt(environ);
 235             equal(environ.get("Foo"), "Bahrein");
 236         } catch (Throwable t) { unexpected(t); }
 237     }
 238 
 239     private static void testVariableModifier(EnvironmentFrobber fooModifier) {
 240         try {
 241             Map<String,String> environ = new ProcessBuilder().environment();
 242             environ.put("Foo","OldValue");
 243             fooModifier.doIt(environ);
 244             equal(environ.get("Foo"), "NewValue");
 245         } catch (Throwable t) { unexpected(t); }
 246     }
 247 
 248     private static void printUTF8(String s) throws IOException {
 249         out.write(s.getBytes("UTF-8"));
 250     }
 251 
 252     private static String getenvAsString(Map<String,String> environment) {
 253         StringBuilder sb = new StringBuilder();
 254         for (Map.Entry<String,String> e : environment.entrySet())
 255             // Ignore magic environment variables added by the launcher
 256             if (! e.getKey().equals("NLSPATH") &&
 257                 ! e.getKey().equals("XFILESEARCHPATH") &&
 258                 ! e.getKey().equals("LD_LIBRARY_PATH"))
 259                 sb.append(e.getKey())
 260                     .append('=')
 261                     .append(e.getValue())
 262                     .append(',');
 263         return sb.toString();
 264     }
 265 
 266     static void print4095(OutputStream s, byte b) throws Throwable {
 267         byte[] bytes = new byte[4095];
 268         Arrays.fill(bytes, b);
 269         s.write(bytes);         // Might hang!
 270     }
 271 
 272     static void checkPermissionDenied(ProcessBuilder pb) {
 273         try {
 274             pb.start();
 275             fail("Expected IOException not thrown");
 276         } catch (IOException e) {
 277             String m = e.getMessage();
 278             if (EnglishUnix.is() &&
 279                 ! matches(m, "Permission denied"))
 280                 unexpected(e);
 281         } catch (Throwable t) { unexpected(t); }
 282     }
 283 
 284     public static class JavaChild {
 285         public static void main(String args[]) throws Throwable {
 286             String action = args[0];
 287             if (action.equals("sleep")) {
 288                 Thread.sleep(10 * 60 * 1000L);
 289             } else if (action.equals("testIO")) {
 290                 String expected = "standard input";
 291                 char[] buf = new char[expected.length()+1];
 292                 int n = new InputStreamReader(System.in).read(buf,0,buf.length);
 293                 if (n != expected.length())
 294                     System.exit(5);
 295                 if (! new String(buf,0,n).equals(expected))
 296                     System.exit(5);
 297                 System.err.print("standard error");
 298                 System.out.print("standard output");
 299             } else if (action.equals("testInheritIO")
 300                     || action.equals("testRedirectInherit")) {
 301                 List<String> childArgs = new ArrayList<String>(javaChildArgs);
 302                 childArgs.add("testIO");
 303                 ProcessBuilder pb = new ProcessBuilder(childArgs);
 304                 if (action.equals("testInheritIO"))
 305                     pb.inheritIO();
 306                 else
 307                     redirectIO(pb, INHERIT, INHERIT, INHERIT);
 308                 ProcessResults r = run(pb);
 309                 if (! r.out().equals(""))
 310                     System.exit(7);
 311                 if (! r.err().equals(""))
 312                     System.exit(8);
 313                 if (r.exitValue() != 0)
 314                     System.exit(9);
 315             } else if (action.equals("System.getenv(String)")) {
 316                 String val = System.getenv(args[1]);
 317                 printUTF8(val == null ? "null" : val);
 318             } else if (action.equals("System.getenv(\\u1234)")) {
 319                 String val = System.getenv("\u1234");
 320                 printUTF8(val == null ? "null" : val);
 321             } else if (action.equals("System.getenv()")) {
 322                 printUTF8(getenvAsString(System.getenv()));
 323             } else if (action.equals("ArrayOOME")) {
 324                 Object dummy;
 325                 switch(new Random().nextInt(3)) {
 326                 case 0: dummy = new Integer[Integer.MAX_VALUE]; break;
 327                 case 1: dummy = new double[Integer.MAX_VALUE];  break;
 328                 case 2: dummy = new byte[Integer.MAX_VALUE][];  break;
 329                 default: throw new InternalError();
 330                 }
 331             } else if (action.equals("pwd")) {
 332                 printUTF8(new File(System.getProperty("user.dir"))
 333                           .getCanonicalPath());
 334             } else if (action.equals("print4095")) {
 335                 print4095(System.out, (byte) '!');
 336                 print4095(System.err, (byte) 'E');
 337                 System.exit(5);
 338             } else if (action.equals("OutErr")) {
 339                 // You might think the system streams would be
 340                 // buffered, and in fact they are implemented using
 341                 // BufferedOutputStream, but each and every print
 342                 // causes immediate operating system I/O.
 343                 System.out.print("out");
 344                 System.err.print("err");
 345                 System.out.print("out");
 346                 System.err.print("err");
 347             } else if (action.equals("null PATH")) {
 348                 equal(System.getenv("PATH"), null);
 349                 check(new File("/bin/true").exists());
 350                 check(new File("/bin/false").exists());
 351                 ProcessBuilder pb1 = new ProcessBuilder();
 352                 ProcessBuilder pb2 = new ProcessBuilder();
 353                 pb2.environment().put("PATH", "anyOldPathIgnoredAnyways");
 354                 ProcessResults r;
 355 
 356                 for (final ProcessBuilder pb :
 357                          new ProcessBuilder[] {pb1, pb2}) {
 358                     pb.command("true");
 359                     equal(run(pb).exitValue(), True.exitValue());
 360 
 361                     pb.command("false");
 362                     equal(run(pb).exitValue(), False.exitValue());
 363                 }
 364 
 365                 if (failed != 0) throw new Error("null PATH");
 366             } else if (action.equals("PATH search algorithm")) {
 367                 equal(System.getenv("PATH"), "dir1:dir2:");
 368                 check(new File("/bin/true").exists());
 369                 check(new File("/bin/false").exists());
 370                 String[] cmd = {"prog"};
 371                 ProcessBuilder pb1 = new ProcessBuilder(cmd);
 372                 ProcessBuilder pb2 = new ProcessBuilder(cmd);
 373                 ProcessBuilder pb3 = new ProcessBuilder(cmd);
 374                 pb2.environment().put("PATH", "anyOldPathIgnoredAnyways");
 375                 pb3.environment().remove("PATH");
 376 
 377                 for (final ProcessBuilder pb :
 378                          new ProcessBuilder[] {pb1, pb2, pb3}) {
 379                     try {
 380                         // Not on PATH at all; directories don't exist
 381                         try {
 382                             pb.start();
 383                             fail("Expected IOException not thrown");
 384                         } catch (IOException e) {
 385                             String m = e.getMessage();
 386                             if (EnglishUnix.is() &&
 387                                 ! matches(m, "No such file"))
 388                                 unexpected(e);
 389                         } catch (Throwable t) { unexpected(t); }
 390 
 391                         // Not on PATH at all; directories exist
 392                         new File("dir1").mkdirs();
 393                         new File("dir2").mkdirs();
 394                         try {
 395                             pb.start();
 396                             fail("Expected IOException not thrown");
 397                         } catch (IOException e) {
 398                             String m = e.getMessage();
 399                             if (EnglishUnix.is() &&
 400                                 ! matches(m, "No such file"))
 401                                 unexpected(e);
 402                         } catch (Throwable t) { unexpected(t); }
 403 
 404                         // Can't execute a directory -- permission denied
 405                         // Report EACCES errno
 406                         new File("dir1/prog").mkdirs();
 407                         checkPermissionDenied(pb);
 408 
 409                         // continue searching if EACCES
 410                         copy("/bin/true", "dir2/prog");
 411                         equal(run(pb).exitValue(), True.exitValue());
 412                         new File("dir1/prog").delete();
 413                         new File("dir2/prog").delete();
 414 
 415                         new File("dir2/prog").mkdirs();
 416                         copy("/bin/true", "dir1/prog");
 417                         equal(run(pb).exitValue(), True.exitValue());
 418 
 419                         // Check empty PATH component means current directory.
 420                         //
 421                         // While we're here, let's test different kinds of
 422                         // Unix executables, and PATH vs explicit searching.
 423                         new File("dir1/prog").delete();
 424                         new File("dir2/prog").delete();
 425                         for (String[] command :
 426                                  new String[][] {
 427                                      new String[] {"./prog"},
 428                                      cmd}) {
 429                             pb.command(command);
 430                             File prog = new File("./prog");
 431                             // "Normal" binaries
 432                             copy("/bin/true", "./prog");
 433                             equal(run(pb).exitValue(),
 434                                   True.exitValue());
 435                             copy("/bin/false", "./prog");
 436                             equal(run(pb).exitValue(),
 437                                   False.exitValue());
 438                             prog.delete();
 439                             // Interpreter scripts with #!
 440                             setFileContents(prog, "#!/bin/true\n");
 441                             prog.setExecutable(true);
 442                             equal(run(pb).exitValue(),
 443                                   True.exitValue());
 444                             prog.delete();
 445                             setFileContents(prog, "#!/bin/false\n");
 446                             prog.setExecutable(true);
 447                             equal(run(pb).exitValue(),
 448                                   False.exitValue());
 449                             // Traditional shell scripts without #!
 450                             setFileContents(prog, "exec /bin/true\n");
 451                             prog.setExecutable(true);
 452                             equal(run(pb).exitValue(),
 453                                   True.exitValue());
 454                             prog.delete();
 455                             setFileContents(prog, "exec /bin/false\n");
 456                             prog.setExecutable(true);
 457                             equal(run(pb).exitValue(),
 458                                   False.exitValue());
 459                             prog.delete();
 460                         }
 461 
 462                         // Test Unix interpreter scripts
 463                         File dir1Prog = new File("dir1/prog");
 464                         dir1Prog.delete();
 465                         pb.command(new String[] {"prog", "world"});
 466                         setFileContents(dir1Prog, "#!/bin/echo hello\n");
 467                         checkPermissionDenied(pb);
 468                         dir1Prog.setExecutable(true);
 469                         equal(run(pb).out(), "hello dir1/prog world\n");
 470                         equal(run(pb).exitValue(), True.exitValue());
 471                         dir1Prog.delete();
 472                         pb.command(cmd);
 473 
 474                         // Test traditional shell scripts without #!
 475                         setFileContents(dir1Prog, "/bin/echo \"$@\"\n");
 476                         pb.command(new String[] {"prog", "hello", "world"});
 477                         checkPermissionDenied(pb);
 478                         dir1Prog.setExecutable(true);
 479                         equal(run(pb).out(), "hello world\n");
 480                         equal(run(pb).exitValue(), True.exitValue());
 481                         dir1Prog.delete();
 482                         pb.command(cmd);
 483 
 484                         // If prog found on both parent and child's PATH,
 485                         // parent's is used.
 486                         new File("dir1/prog").delete();
 487                         new File("dir2/prog").delete();
 488                         new File("prog").delete();
 489                         new File("dir3").mkdirs();
 490                         copy("/bin/true", "dir1/prog");
 491                         copy("/bin/false", "dir3/prog");
 492                         pb.environment().put("PATH","dir3");
 493                         equal(run(pb).exitValue(), True.exitValue());
 494                         copy("/bin/true", "dir3/prog");
 495                         copy("/bin/false", "dir1/prog");
 496                         equal(run(pb).exitValue(), False.exitValue());
 497 
 498                     } finally {
 499                         // cleanup
 500                         new File("dir1/prog").delete();
 501                         new File("dir2/prog").delete();
 502                         new File("dir3/prog").delete();
 503                         new File("dir1").delete();
 504                         new File("dir2").delete();
 505                         new File("dir3").delete();
 506                         new File("prog").delete();
 507                     }
 508                 }
 509 
 510                 if (failed != 0) throw new Error("PATH search algorithm");
 511             }
 512             else throw new Error("JavaChild invocation error");
 513         }
 514     }
 515 
 516     private static void copy(String src, String dst) {
 517         system("/bin/cp", "-fp", src, dst);
 518     }
 519 
 520     private static void system(String... command) {
 521         try {
 522             ProcessBuilder pb = new ProcessBuilder(command);
 523             ProcessResults r = run(pb.start());
 524             equal(r.exitValue(), 0);
 525             equal(r.out(), "");
 526             equal(r.err(), "");
 527         } catch (Throwable t) { unexpected(t); }
 528     }
 529 
 530     private static String javaChildOutput(ProcessBuilder pb, String...args) {
 531         List<String> list = new ArrayList<String>(javaChildArgs);
 532         for (String arg : args)
 533             list.add(arg);
 534         pb.command(list);
 535         return commandOutput(pb);
 536     }
 537 
 538     private static String getenvInChild(ProcessBuilder pb) {
 539         return javaChildOutput(pb, "System.getenv()");
 540     }
 541 
 542     private static String getenvInChild1234(ProcessBuilder pb) {
 543         return javaChildOutput(pb, "System.getenv(\\u1234)");
 544     }
 545 
 546     private static String getenvInChild(ProcessBuilder pb, String name) {
 547         return javaChildOutput(pb, "System.getenv(String)", name);
 548     }
 549 
 550     private static String pwdInChild(ProcessBuilder pb) {
 551         return javaChildOutput(pb, "pwd");
 552     }
 553 
 554     private static final String javaExe =
 555         System.getProperty("java.home") +
 556         File.separator + "bin" + File.separator + "java";
 557 
 558     private static final String classpath =
 559         System.getProperty("java.class.path");
 560 
 561     private static final List<String> javaChildArgs =
 562         Arrays.asList(new String[]
 563             { javaExe, "-classpath", absolutifyPath(classpath),
 564               "Basic$JavaChild"});
 565 
 566     private static void testEncoding(String encoding, String tested) {
 567         try {
 568             // If round trip conversion works, should be able to set env vars
 569             // correctly in child.
 570             if (new String(tested.getBytes()).equals(tested)) {
 571                 out.println("Testing " + encoding + " environment values");
 572                 ProcessBuilder pb = new ProcessBuilder();
 573                 pb.environment().put("ASCIINAME",tested);
 574                 equal(getenvInChild(pb,"ASCIINAME"), tested);
 575             }
 576         } catch (Throwable t) { unexpected(t); }
 577     }
 578 
 579     static class Windows {
 580         public static boolean is() { return is; }
 581         private static final boolean is =
 582             System.getProperty("os.name").startsWith("Windows");
 583     }
 584 
 585     static class Unix {
 586         public static boolean is() { return is; }
 587         private static final boolean is =
 588             (! Windows.is() &&
 589              new File("/bin/sh").exists() &&
 590              new File("/bin/true").exists() &&
 591              new File("/bin/false").exists());
 592     }
 593 
 594     static class UnicodeOS {
 595         public static boolean is() { return is; }
 596         private static final String osName = System.getProperty("os.name");
 597         private static final boolean is =
 598             // MacOS X would probably also qualify
 599             osName.startsWith("Windows")   &&
 600             ! osName.startsWith("Windows 9") &&
 601             ! osName.equals("Windows Me");
 602     }
 603 
 604     static class MacOSX {
 605         public static boolean is() { return is; }
 606         private static final String osName = System.getProperty("os.name");
 607         private static final boolean is = osName.contains("OS X");
 608     }
 609 
 610     static class True {
 611         public static int exitValue() { return 0; }
 612     }
 613 
 614     private static class False {
 615         public static int exitValue() { return exitValue; }
 616         private static final int exitValue = exitValue0();
 617         private static int exitValue0() {
 618             // /bin/false returns an *unspecified* non-zero number.
 619             try {
 620                 if (! Unix.is())
 621                     return -1;
 622                 else {
 623                     int rc = new ProcessBuilder("/bin/false")
 624                         .start().waitFor();
 625                     check(rc != 0);
 626                     return rc;
 627                 }
 628             } catch (Throwable t) { unexpected(t); return -1; }
 629         }
 630     }
 631 
 632     static class EnglishUnix {
 633         private final static Boolean is =
 634             (! Windows.is() && isEnglish("LANG") && isEnglish("LC_ALL"));
 635 
 636         private static boolean isEnglish(String envvar) {
 637             String val = getenv(envvar);
 638             return (val == null) || val.matches("en.*");
 639         }
 640 
 641         /** Returns true if we can expect English OS error strings */
 642         static boolean is() { return is; }
 643     }
 644 
 645     private static boolean matches(String str, String regex) {
 646         return Pattern.compile(regex).matcher(str).find();
 647     }
 648 
 649     private static String matchAndExtract(String str, String regex) {
 650         Matcher matcher = Pattern.compile(regex).matcher(str);
 651         if (matcher.find()) {
 652             return matcher.group();
 653         } else {
 654             return "";
 655         }
 656     }
 657 
 658     /* Only used for Mac OS X --
 659      * Mac OS X adds the variable __CF_USER_TEXT_ENCODING to an empty
 660      * environment. The environment variable JAVA_MAIN_CLASS_<pid> should also
 661      * be set in Mac OS X.
 662      * Check for both by removing them both from the list of env variables.
 663      */
 664     private static String removeMacExpectedVars(String vars) {
 665         // Check for __CF_USER_TEXT_ENCODING
 666         String cleanedVars = vars.replace("__CF_USER_TEXT_ENCODING="
 667                                             +cfUserTextEncoding+",","");
 668         if (cleanedVars.equals(vars)) {
 669             fail("Environment variable __CF_USER_TEXT_ENCODING not set. "
 670                  + "MAC OS X should set __CF_USER_TEXT_ENCODING in "
 671                  + "an empty environment.");
 672         }
 673 
 674         // Check for JAVA_MAIN_CLASS_<pid>
 675         String javaMainClassStr
 676                 = matchAndExtract(cleanedVars,
 677                                     "JAVA_MAIN_CLASS_\\d+=Basic.JavaChild,");
 678         if (javaMainClassStr.equals("")) {
 679             fail("JAVA_MAIN_CLASS_<pid> not set. "
 680                     + "Should be set in Mac OS X env.");
 681         }
 682         return cleanedVars.replace(javaMainClassStr,"");
 683     }
 684 
 685     private static String sortByLinesWindowsly(String text) {
 686         String[] lines = text.split("\n");
 687         Arrays.sort(lines, new WindowsComparator());
 688         StringBuilder sb = new StringBuilder();
 689         for (String line : lines)
 690             sb.append(line).append("\n");
 691         return sb.toString();
 692     }
 693 
 694     private static void checkMapSanity(Map<String,String> map) {
 695         try {
 696             Set<String> keySet = map.keySet();
 697             Collection<String> values = map.values();
 698             Set<Map.Entry<String,String>> entrySet = map.entrySet();
 699 
 700             equal(entrySet.size(), keySet.size());
 701             equal(entrySet.size(), values.size());
 702 
 703             StringBuilder s1 = new StringBuilder();
 704             for (Map.Entry<String,String> e : entrySet)
 705                 s1.append(e.getKey() + "=" + e.getValue() + "\n");
 706 
 707             StringBuilder s2 = new StringBuilder();
 708             for (String var : keySet)
 709                 s2.append(var + "=" + map.get(var) + "\n");
 710 
 711             equal(s1.toString(), s2.toString());
 712 
 713             Iterator<String> kIter = keySet.iterator();
 714             Iterator<String> vIter = values.iterator();
 715             Iterator<Map.Entry<String,String>> eIter = entrySet.iterator();
 716 
 717             while (eIter.hasNext()) {
 718                 Map.Entry<String,String> entry = eIter.next();
 719                 String key   = kIter.next();
 720                 String value = vIter.next();
 721                 check(entrySet.contains(entry));
 722                 check(keySet.contains(key));
 723                 check(values.contains(value));
 724                 check(map.containsKey(key));
 725                 check(map.containsValue(value));
 726                 equal(entry.getKey(), key);
 727                 equal(entry.getValue(), value);
 728             }
 729             check(! kIter.hasNext() &&
 730                   ! vIter.hasNext());
 731 
 732         } catch (Throwable t) { unexpected(t); }
 733     }
 734 
 735     private static void checkMapEquality(Map<String,String> map1,
 736                                          Map<String,String> map2) {
 737         try {
 738             equal(map1.size(), map2.size());
 739             equal(map1.isEmpty(), map2.isEmpty());
 740             for (String key : map1.keySet()) {
 741                 equal(map1.get(key), map2.get(key));
 742                 check(map2.keySet().contains(key));
 743             }
 744             equal(map1, map2);
 745             equal(map2, map1);
 746             equal(map1.entrySet(), map2.entrySet());
 747             equal(map2.entrySet(), map1.entrySet());
 748             equal(map1.keySet(), map2.keySet());
 749             equal(map2.keySet(), map1.keySet());
 750 
 751             equal(map1.hashCode(), map2.hashCode());
 752             equal(map1.entrySet().hashCode(), map2.entrySet().hashCode());
 753             equal(map1.keySet().hashCode(), map2.keySet().hashCode());
 754         } catch (Throwable t) { unexpected(t); }
 755     }
 756 
 757     static void checkRedirects(ProcessBuilder pb,
 758                                Redirect in, Redirect out, Redirect err) {
 759         equal(pb.redirectInput(),  in);
 760         equal(pb.redirectOutput(), out);
 761         equal(pb.redirectError(),  err);
 762     }
 763 
 764     static void redirectIO(ProcessBuilder pb,
 765                            Redirect in, Redirect out, Redirect err) {
 766         pb.redirectInput(in);
 767         pb.redirectOutput(out);
 768         pb.redirectError(err);
 769     }
 770 
 771     static void setFileContents(File file, String contents) {
 772         try {
 773             Writer w = new FileWriter(file);
 774             w.write(contents);
 775             w.close();
 776         } catch (Throwable t) { unexpected(t); }
 777     }
 778 
 779     static String fileContents(File file) {
 780         try {
 781             Reader r = new FileReader(file);
 782             StringBuilder sb = new StringBuilder();
 783             char[] buffer = new char[1024];
 784             int n;
 785             while ((n = r.read(buffer)) != -1)
 786                 sb.append(buffer,0,n);
 787             r.close();
 788             return new String(sb);
 789         } catch (Throwable t) { unexpected(t); return ""; }
 790     }
 791 
 792     static void testIORedirection() throws Throwable {
 793         final File ifile = new File("ifile");
 794         final File ofile = new File("ofile");
 795         final File efile = new File("efile");
 796         ifile.delete();
 797         ofile.delete();
 798         efile.delete();
 799 
 800         //----------------------------------------------------------------
 801         // Check mutual inequality of different types of Redirect
 802         //----------------------------------------------------------------
 803         Redirect[] redirects =
 804             { PIPE,
 805               INHERIT,
 806               Redirect.from(ifile),
 807               Redirect.to(ifile),
 808               Redirect.appendTo(ifile),
 809               Redirect.from(ofile),
 810               Redirect.to(ofile),
 811               Redirect.appendTo(ofile),
 812             };
 813         for (int i = 0; i < redirects.length; i++)
 814             for (int j = 0; j < redirects.length; j++)
 815                 equal(redirects[i].equals(redirects[j]), (i == j));
 816 
 817         //----------------------------------------------------------------
 818         // Check basic properties of different types of Redirect
 819         //----------------------------------------------------------------
 820         equal(PIPE.type(), Redirect.Type.PIPE);
 821         equal(PIPE.toString(), "PIPE");
 822         equal(PIPE.file(), null);
 823 
 824         equal(INHERIT.type(), Redirect.Type.INHERIT);
 825         equal(INHERIT.toString(), "INHERIT");
 826         equal(INHERIT.file(), null);
 827 
 828         equal(Redirect.from(ifile).type(), Redirect.Type.READ);
 829         equal(Redirect.from(ifile).toString(),
 830               "redirect to read from file \"ifile\"");
 831         equal(Redirect.from(ifile).file(), ifile);
 832         equal(Redirect.from(ifile),
 833               Redirect.from(ifile));
 834         equal(Redirect.from(ifile).hashCode(),
 835               Redirect.from(ifile).hashCode());
 836 
 837         equal(Redirect.to(ofile).type(), Redirect.Type.WRITE);
 838         equal(Redirect.to(ofile).toString(),
 839               "redirect to write to file \"ofile\"");
 840         equal(Redirect.to(ofile).file(), ofile);
 841         equal(Redirect.to(ofile),
 842               Redirect.to(ofile));
 843         equal(Redirect.to(ofile).hashCode(),
 844               Redirect.to(ofile).hashCode());
 845 
 846         equal(Redirect.appendTo(ofile).type(), Redirect.Type.APPEND);
 847         equal(Redirect.appendTo(efile).toString(),
 848               "redirect to append to file \"efile\"");
 849         equal(Redirect.appendTo(efile).file(), efile);
 850         equal(Redirect.appendTo(efile),
 851               Redirect.appendTo(efile));
 852         equal(Redirect.appendTo(efile).hashCode(),
 853               Redirect.appendTo(efile).hashCode());
 854 
 855         //----------------------------------------------------------------
 856         // Check initial values of redirects
 857         //----------------------------------------------------------------
 858         List<String> childArgs = new ArrayList<String>(javaChildArgs);
 859         childArgs.add("testIO");
 860         final ProcessBuilder pb = new ProcessBuilder(childArgs);
 861         checkRedirects(pb, PIPE, PIPE, PIPE);
 862 
 863         //----------------------------------------------------------------
 864         // Check inheritIO
 865         //----------------------------------------------------------------
 866         pb.inheritIO();
 867         checkRedirects(pb, INHERIT, INHERIT, INHERIT);
 868 
 869         //----------------------------------------------------------------
 870         // Check setters and getters agree
 871         //----------------------------------------------------------------
 872         pb.redirectInput(ifile);
 873         equal(pb.redirectInput().file(), ifile);
 874         equal(pb.redirectInput(), Redirect.from(ifile));
 875 
 876         pb.redirectOutput(ofile);
 877         equal(pb.redirectOutput().file(), ofile);
 878         equal(pb.redirectOutput(), Redirect.to(ofile));
 879 
 880         pb.redirectError(efile);
 881         equal(pb.redirectError().file(), efile);
 882         equal(pb.redirectError(), Redirect.to(efile));
 883 
 884         THROWS(IllegalArgumentException.class,
 885             new Fun(){void f() {
 886                 pb.redirectInput(Redirect.to(ofile)); }},
 887             new Fun(){void f() {
 888                 pb.redirectInput(Redirect.appendTo(ofile)); }},
 889             new Fun(){void f() {
 890                 pb.redirectOutput(Redirect.from(ifile)); }},
 891             new Fun(){void f() {
 892                 pb.redirectError(Redirect.from(ifile)); }});
 893 
 894         THROWS(IOException.class,
 895                // Input file does not exist
 896                new Fun(){void f() throws Throwable { pb.start(); }});
 897         setFileContents(ifile, "standard input");
 898 
 899         //----------------------------------------------------------------
 900         // Writing to non-existent files
 901         //----------------------------------------------------------------
 902         {
 903             ProcessResults r = run(pb);
 904             equal(r.exitValue(), 0);
 905             equal(fileContents(ofile), "standard output");
 906             equal(fileContents(efile), "standard error");
 907             equal(r.out(), "");
 908             equal(r.err(), "");
 909             ofile.delete();
 910             efile.delete();
 911         }
 912 
 913         //----------------------------------------------------------------
 914         // Both redirectErrorStream + redirectError
 915         //----------------------------------------------------------------
 916         {
 917             pb.redirectErrorStream(true);
 918             ProcessResults r = run(pb);
 919             equal(r.exitValue(), 0);
 920             equal(fileContents(ofile),
 921                   "standard error" + "standard output");
 922             equal(fileContents(efile), "");
 923             equal(r.out(), "");
 924             equal(r.err(), "");
 925             ofile.delete();
 926             efile.delete();
 927         }
 928 
 929         //----------------------------------------------------------------
 930         // Appending to existing files
 931         //----------------------------------------------------------------
 932         {
 933             setFileContents(ofile, "ofile-contents");
 934             setFileContents(efile, "efile-contents");
 935             pb.redirectOutput(Redirect.appendTo(ofile));
 936             pb.redirectError(Redirect.appendTo(efile));
 937             pb.redirectErrorStream(false);
 938             ProcessResults r = run(pb);
 939             equal(r.exitValue(), 0);
 940             equal(fileContents(ofile),
 941                   "ofile-contents" + "standard output");
 942             equal(fileContents(efile),
 943                   "efile-contents" + "standard error");
 944             equal(r.out(), "");
 945             equal(r.err(), "");
 946             ofile.delete();
 947             efile.delete();
 948         }
 949 
 950         //----------------------------------------------------------------
 951         // Replacing existing files
 952         //----------------------------------------------------------------
 953         {
 954             setFileContents(ofile, "ofile-contents");
 955             setFileContents(efile, "efile-contents");
 956             pb.redirectOutput(ofile);
 957             pb.redirectError(Redirect.to(efile));
 958             ProcessResults r = run(pb);
 959             equal(r.exitValue(), 0);
 960             equal(fileContents(ofile), "standard output");
 961             equal(fileContents(efile), "standard error");
 962             equal(r.out(), "");
 963             equal(r.err(), "");
 964             ofile.delete();
 965             efile.delete();
 966         }
 967 
 968         //----------------------------------------------------------------
 969         // Appending twice to the same file?
 970         //----------------------------------------------------------------
 971         {
 972             setFileContents(ofile, "ofile-contents");
 973             setFileContents(efile, "efile-contents");
 974             Redirect appender = Redirect.appendTo(ofile);
 975             pb.redirectOutput(appender);
 976             pb.redirectError(appender);
 977             ProcessResults r = run(pb);
 978             equal(r.exitValue(), 0);
 979             equal(fileContents(ofile),
 980                   "ofile-contents" +
 981                   "standard error" +
 982                   "standard output");
 983             equal(fileContents(efile), "efile-contents");
 984             equal(r.out(), "");
 985             equal(r.err(), "");
 986             ifile.delete();
 987             ofile.delete();
 988             efile.delete();
 989         }
 990 
 991         //----------------------------------------------------------------
 992         // Testing INHERIT is harder.
 993         // Note that this requires __FOUR__ nested JVMs involved in one test,
 994         // if you count the harness JVM.
 995         //----------------------------------------------------------------
 996         for (String testName : new String[] { "testInheritIO", "testRedirectInherit" } ) {
 997             redirectIO(pb, PIPE, PIPE, PIPE);
 998             List<String> command = pb.command();
 999             command.set(command.size() - 1, testName);
1000             Process p = pb.start();
1001             new PrintStream(p.getOutputStream()).print("standard input");
1002             p.getOutputStream().close();
1003             ProcessResults r = run(p);
1004             equal(r.exitValue(), 0);
1005             equal(r.out(), "standard output");
1006             equal(r.err(), "standard error");
1007         }
1008 
1009         //----------------------------------------------------------------
1010         // Test security implications of I/O redirection
1011         //----------------------------------------------------------------
1012 
1013         // Read access to current directory is always granted;
1014         // So create a tmpfile for input instead.
1015         final File tmpFile = File.createTempFile("Basic", "tmp");
1016         setFileContents(tmpFile, "standard input");
1017 
1018         final Policy policy = new Policy();
1019         Policy.setPolicy(policy);
1020         System.setSecurityManager(new SecurityManager());
1021         try {
1022             final Permission xPermission
1023                 = new FilePermission("<<ALL FILES>>", "execute");
1024             final Permission rxPermission
1025                 = new FilePermission("<<ALL FILES>>", "read,execute");
1026             final Permission wxPermission
1027                 = new FilePermission("<<ALL FILES>>", "write,execute");
1028             final Permission rwxPermission
1029                 = new FilePermission("<<ALL FILES>>", "read,write,execute");
1030 
1031             THROWS(SecurityException.class,
1032                new Fun() { void f() throws IOException {
1033                    policy.setPermissions(xPermission);
1034                    redirectIO(pb, from(tmpFile), PIPE, PIPE);
1035                    pb.start();}},
1036                new Fun() { void f() throws IOException {
1037                    policy.setPermissions(rxPermission);
1038                    redirectIO(pb, PIPE, to(ofile), PIPE);
1039                    pb.start();}},
1040                new Fun() { void f() throws IOException {
1041                    policy.setPermissions(rxPermission);
1042                    redirectIO(pb, PIPE, PIPE, to(efile));
1043                    pb.start();}});
1044 
1045             {
1046                 policy.setPermissions(rxPermission);
1047                 redirectIO(pb, from(tmpFile), PIPE, PIPE);
1048                 ProcessResults r = run(pb);
1049                 equal(r.out(), "standard output");
1050                 equal(r.err(), "standard error");
1051             }
1052 
1053             {
1054                 policy.setPermissions(wxPermission);
1055                 redirectIO(pb, PIPE, to(ofile), to(efile));
1056                 Process p = pb.start();
1057                 new PrintStream(p.getOutputStream()).print("standard input");
1058                 p.getOutputStream().close();
1059                 ProcessResults r = run(p);
1060                 policy.setPermissions(rwxPermission);
1061                 equal(fileContents(ofile), "standard output");
1062                 equal(fileContents(efile), "standard error");
1063             }
1064 
1065             {
1066                 policy.setPermissions(rwxPermission);
1067                 redirectIO(pb, from(tmpFile), to(ofile), to(efile));
1068                 ProcessResults r = run(pb);
1069                 policy.setPermissions(rwxPermission);
1070                 equal(fileContents(ofile), "standard output");
1071                 equal(fileContents(efile), "standard error");
1072             }
1073 
1074         } finally {
1075             policy.setPermissions(new RuntimePermission("setSecurityManager"));
1076             System.setSecurityManager(null);
1077             tmpFile.delete();
1078             ifile.delete();
1079             ofile.delete();
1080             efile.delete();
1081         }
1082     }
1083 
1084     private static void realMain(String[] args) throws Throwable {
1085         if (Windows.is())
1086             System.out.println("This appears to be a Windows system.");
1087         if (Unix.is())
1088             System.out.println("This appears to be a Unix system.");
1089         if (UnicodeOS.is())
1090             System.out.println("This appears to be a Unicode-based OS.");
1091 
1092         try { testIORedirection(); }
1093         catch (Throwable t) { unexpected(t); }
1094 
1095         //----------------------------------------------------------------
1096         // Basic tests for setting, replacing and deleting envvars
1097         //----------------------------------------------------------------
1098         try {
1099             ProcessBuilder pb = new ProcessBuilder();
1100             Map<String,String> environ = pb.environment();
1101 
1102             // New env var
1103             environ.put("QUUX", "BAR");
1104             equal(environ.get("QUUX"), "BAR");
1105             equal(getenvInChild(pb,"QUUX"), "BAR");
1106 
1107             // Modify env var
1108             environ.put("QUUX","bear");
1109             equal(environ.get("QUUX"), "bear");
1110             equal(getenvInChild(pb,"QUUX"), "bear");
1111             checkMapSanity(environ);
1112 
1113             // Remove env var
1114             environ.remove("QUUX");
1115             equal(environ.get("QUUX"), null);
1116             equal(getenvInChild(pb,"QUUX"), "null");
1117             checkMapSanity(environ);
1118 
1119             // Remove non-existent env var
1120             environ.remove("QUUX");
1121             equal(environ.get("QUUX"), null);
1122             equal(getenvInChild(pb,"QUUX"), "null");
1123             checkMapSanity(environ);
1124         } catch (Throwable t) { unexpected(t); }
1125 
1126         //----------------------------------------------------------------
1127         // Pass Empty environment to child
1128         //----------------------------------------------------------------
1129         try {
1130             ProcessBuilder pb = new ProcessBuilder();
1131             pb.environment().clear();
1132             String expected = Windows.is() ? "SystemRoot="+systemRoot+",": "";
1133             if (Windows.is()) {
1134                 pb.environment().put("SystemRoot", systemRoot);
1135             }
1136             String result = getenvInChild(pb);
1137             if (MacOSX.is()) {
1138                 result = removeMacExpectedVars(result);
1139             }
1140             equal(result, expected);
1141         } catch (Throwable t) { unexpected(t); }
1142 
1143         //----------------------------------------------------------------
1144         // System.getenv() is read-only.
1145         //----------------------------------------------------------------
1146         THROWS(UnsupportedOperationException.class,
1147             new Fun(){void f(){ getenv().put("FOO","BAR");}},
1148             new Fun(){void f(){ getenv().remove("PATH");}},
1149             new Fun(){void f(){ getenv().keySet().remove("PATH");}},
1150             new Fun(){void f(){ getenv().values().remove("someValue");}});
1151 
1152         try {
1153             Collection<Map.Entry<String,String>> c = getenv().entrySet();
1154             if (! c.isEmpty())
1155                 try {
1156                     c.iterator().next().setValue("foo");
1157                     fail("Expected UnsupportedOperationException not thrown");
1158                 } catch (UnsupportedOperationException e) {} // OK
1159         } catch (Throwable t) { unexpected(t); }
1160 
1161         //----------------------------------------------------------------
1162         // System.getenv() always returns the same object in our implementation.
1163         //----------------------------------------------------------------
1164         try {
1165             check(System.getenv() == System.getenv());
1166         } catch (Throwable t) { unexpected(t); }
1167 
1168         //----------------------------------------------------------------
1169         // You can't create an env var name containing "=",
1170         // or an env var name or value containing NUL.
1171         //----------------------------------------------------------------
1172         {
1173             final Map<String,String> m = new ProcessBuilder().environment();
1174             THROWS(IllegalArgumentException.class,
1175                 new Fun(){void f(){ m.put("FOO=","BAR");}},
1176                 new Fun(){void f(){ m.put("FOO\u0000","BAR");}},
1177                 new Fun(){void f(){ m.put("FOO","BAR\u0000");}});
1178         }
1179 
1180         //----------------------------------------------------------------
1181         // Commands must never be null.
1182         //----------------------------------------------------------------
1183         THROWS(NullPointerException.class,
1184                new Fun(){void f(){
1185                    new ProcessBuilder((List<String>)null);}},
1186                new Fun(){void f(){
1187                    new ProcessBuilder().command((List<String>)null);}});
1188 
1189         //----------------------------------------------------------------
1190         // Put in a command; get the same one back out.
1191         //----------------------------------------------------------------
1192         try {
1193             List<String> command = new ArrayList<String>();
1194             ProcessBuilder pb = new ProcessBuilder(command);
1195             check(pb.command() == command);
1196             List<String> command2 = new ArrayList<String>(2);
1197             command2.add("foo");
1198             command2.add("bar");
1199             pb.command(command2);
1200             check(pb.command() == command2);
1201             pb.command("foo", "bar");
1202             check(pb.command() != command2 && pb.command().equals(command2));
1203             pb.command(command2);
1204             command2.add("baz");
1205             equal(pb.command().get(2), "baz");
1206         } catch (Throwable t) { unexpected(t); }
1207 
1208         //----------------------------------------------------------------
1209         // Commands must contain at least one element.
1210         //----------------------------------------------------------------
1211         THROWS(IndexOutOfBoundsException.class,
1212             new Fun() { void f() throws IOException {
1213                 new ProcessBuilder().start();}},
1214             new Fun() { void f() throws IOException {
1215                 new ProcessBuilder(new ArrayList<String>()).start();}},
1216             new Fun() { void f() throws IOException {
1217                 Runtime.getRuntime().exec(new String[]{});}});
1218 
1219         //----------------------------------------------------------------
1220         // Commands must not contain null elements at start() time.
1221         //----------------------------------------------------------------
1222         THROWS(NullPointerException.class,
1223             new Fun() { void f() throws IOException {
1224                 new ProcessBuilder("foo",null,"bar").start();}},
1225             new Fun() { void f() throws IOException {
1226                 new ProcessBuilder((String)null).start();}},
1227             new Fun() { void f() throws IOException {
1228                 new ProcessBuilder(new String[]{null}).start();}},
1229             new Fun() { void f() throws IOException {
1230                 new ProcessBuilder(new String[]{"foo",null,"bar"}).start();}});
1231 
1232         //----------------------------------------------------------------
1233         // Command lists are growable.
1234         //----------------------------------------------------------------
1235         try {
1236             new ProcessBuilder().command().add("foo");
1237             new ProcessBuilder("bar").command().add("foo");
1238             new ProcessBuilder(new String[]{"1","2"}).command().add("3");
1239         } catch (Throwable t) { unexpected(t); }
1240 
1241         //----------------------------------------------------------------
1242         // Nulls in environment updates generate NullPointerException
1243         //----------------------------------------------------------------
1244         try {
1245             final Map<String,String> env = new ProcessBuilder().environment();
1246             THROWS(NullPointerException.class,
1247                 new Fun(){void f(){ env.put("foo",null);}},
1248                 new Fun(){void f(){ env.put(null,"foo");}},
1249                 new Fun(){void f(){ env.remove(null);}},
1250                 new Fun(){void f(){
1251                     for (Map.Entry<String,String> e : env.entrySet())
1252                         e.setValue(null);}},
1253                 new Fun() { void f() throws IOException {
1254                     Runtime.getRuntime().exec(new String[]{"foo"},
1255                                               new String[]{null});}});
1256         } catch (Throwable t) { unexpected(t); }
1257 
1258         //----------------------------------------------------------------
1259         // Non-String types in environment updates generate ClassCastException
1260         //----------------------------------------------------------------
1261         try {
1262             final Map<String,String> env = new ProcessBuilder().environment();
1263             THROWS(ClassCastException.class,
1264                 new Fun(){void f(){ env.remove(TRUE);}},
1265                 new Fun(){void f(){ env.keySet().remove(TRUE);}},
1266                 new Fun(){void f(){ env.values().remove(TRUE);}},
1267                 new Fun(){void f(){ env.entrySet().remove(TRUE);}});
1268         } catch (Throwable t) { unexpected(t); }
1269 
1270         //----------------------------------------------------------------
1271         // Check query operations on environment maps
1272         //----------------------------------------------------------------
1273         try {
1274             List<Map<String,String>> envs =
1275                 new ArrayList<Map<String,String>>(2);
1276             envs.add(System.getenv());
1277             envs.add(new ProcessBuilder().environment());
1278             for (final Map<String,String> env : envs) {
1279                 //----------------------------------------------------------------
1280                 // Nulls in environment queries are forbidden.
1281                 //----------------------------------------------------------------
1282                 THROWS(NullPointerException.class,
1283                     new Fun(){void f(){ getenv(null);}},
1284                     new Fun(){void f(){ env.get(null);}},
1285                     new Fun(){void f(){ env.containsKey(null);}},
1286                     new Fun(){void f(){ env.containsValue(null);}},
1287                     new Fun(){void f(){ env.keySet().contains(null);}},
1288                     new Fun(){void f(){ env.values().contains(null);}});
1289 
1290                 //----------------------------------------------------------------
1291                 // Non-String types in environment queries are forbidden.
1292                 //----------------------------------------------------------------
1293                 THROWS(ClassCastException.class,
1294                     new Fun(){void f(){ env.get(TRUE);}},
1295                     new Fun(){void f(){ env.containsKey(TRUE);}},
1296                     new Fun(){void f(){ env.containsValue(TRUE);}},
1297                     new Fun(){void f(){ env.keySet().contains(TRUE);}},
1298                     new Fun(){void f(){ env.values().contains(TRUE);}});
1299 
1300                 //----------------------------------------------------------------
1301                 // Illegal String values in environment queries are (grumble) OK
1302                 //----------------------------------------------------------------
1303                 equal(env.get("\u0000"), null);
1304                 check(! env.containsKey("\u0000"));
1305                 check(! env.containsValue("\u0000"));
1306                 check(! env.keySet().contains("\u0000"));
1307                 check(! env.values().contains("\u0000"));
1308             }
1309 
1310         } catch (Throwable t) { unexpected(t); }
1311 
1312         try {
1313             final Set<Map.Entry<String,String>> entrySet =
1314                 new ProcessBuilder().environment().entrySet();
1315             THROWS(NullPointerException.class,
1316                    new Fun(){void f(){ entrySet.contains(null);}});
1317             THROWS(ClassCastException.class,
1318                 new Fun(){void f(){ entrySet.contains(TRUE);}},
1319                 new Fun(){void f(){
1320                     entrySet.contains(
1321                         new SimpleImmutableEntry<Boolean,String>(TRUE,""));}});
1322 
1323             check(! entrySet.contains
1324                   (new SimpleImmutableEntry<String,String>("", "")));
1325         } catch (Throwable t) { unexpected(t); }
1326 
1327         //----------------------------------------------------------------
1328         // Put in a directory; get the same one back out.
1329         //----------------------------------------------------------------
1330         try {
1331             ProcessBuilder pb = new ProcessBuilder();
1332             File foo = new File("foo");
1333             equal(pb.directory(), null);
1334             equal(pb.directory(foo).directory(), foo);
1335             equal(pb.directory(null).directory(), null);
1336         } catch (Throwable t) { unexpected(t); }
1337 
1338         //----------------------------------------------------------------
1339         // If round-trip conversion works, check envvar pass-through to child
1340         //----------------------------------------------------------------
1341         try {
1342             testEncoding("ASCII",   "xyzzy");
1343             testEncoding("Latin1",  "\u00f1\u00e1");
1344             testEncoding("Unicode", "\u22f1\u11e1");
1345         } catch (Throwable t) { unexpected(t); }
1346 
1347         //----------------------------------------------------------------
1348         // A surprisingly large number of ways to delete an environment var.
1349         //----------------------------------------------------------------
1350         testVariableDeleter(new EnvironmentFrobber() {
1351                 public void doIt(Map<String,String> environ) {
1352                     environ.remove("Foo");}});
1353 
1354         testVariableDeleter(new EnvironmentFrobber() {
1355                 public void doIt(Map<String,String> environ) {
1356                     environ.keySet().remove("Foo");}});
1357 
1358         testVariableDeleter(new EnvironmentFrobber() {
1359                 public void doIt(Map<String,String> environ) {
1360                     environ.values().remove("BAAR");}});
1361 
1362         testVariableDeleter(new EnvironmentFrobber() {
1363                 public void doIt(Map<String,String> environ) {
1364                     // Legally fabricate a ProcessEnvironment.StringEntry,
1365                     // even though it's private.
1366                     Map<String,String> environ2
1367                         = new ProcessBuilder().environment();
1368                     environ2.clear();
1369                     environ2.put("Foo","BAAR");
1370                     // Subtlety alert.
1371                     Map.Entry<String,String> e
1372                         = environ2.entrySet().iterator().next();
1373                     environ.entrySet().remove(e);}});
1374 
1375         testVariableDeleter(new EnvironmentFrobber() {
1376                 public void doIt(Map<String,String> environ) {
1377                     Map.Entry<String,String> victim = null;
1378                     for (Map.Entry<String,String> e : environ.entrySet())
1379                         if (e.getKey().equals("Foo"))
1380                             victim = e;
1381                     if (victim != null)
1382                         environ.entrySet().remove(victim);}});
1383 
1384         testVariableDeleter(new EnvironmentFrobber() {
1385                 public void doIt(Map<String,String> environ) {
1386                     Iterator<String> it = environ.keySet().iterator();
1387                     while (it.hasNext()) {
1388                         String val = it.next();
1389                         if (val.equals("Foo"))
1390                             it.remove();}}});
1391 
1392         testVariableDeleter(new EnvironmentFrobber() {
1393                 public void doIt(Map<String,String> environ) {
1394                     Iterator<Map.Entry<String,String>> it
1395                         = environ.entrySet().iterator();
1396                     while (it.hasNext()) {
1397                         Map.Entry<String,String> e = it.next();
1398                         if (e.getKey().equals("Foo"))
1399                             it.remove();}}});
1400 
1401         testVariableDeleter(new EnvironmentFrobber() {
1402                 public void doIt(Map<String,String> environ) {
1403                     Iterator<String> it = environ.values().iterator();
1404                     while (it.hasNext()) {
1405                         String val = it.next();
1406                         if (val.equals("BAAR"))
1407                             it.remove();}}});
1408 
1409         //----------------------------------------------------------------
1410         // A surprisingly small number of ways to add an environment var.
1411         //----------------------------------------------------------------
1412         testVariableAdder(new EnvironmentFrobber() {
1413                 public void doIt(Map<String,String> environ) {
1414                     environ.put("Foo","Bahrein");}});
1415 
1416         //----------------------------------------------------------------
1417         // A few ways to modify an environment var.
1418         //----------------------------------------------------------------
1419         testVariableModifier(new EnvironmentFrobber() {
1420                 public void doIt(Map<String,String> environ) {
1421                     environ.put("Foo","NewValue");}});
1422 
1423         testVariableModifier(new EnvironmentFrobber() {
1424                 public void doIt(Map<String,String> environ) {
1425                     for (Map.Entry<String,String> e : environ.entrySet())
1426                         if (e.getKey().equals("Foo"))
1427                             e.setValue("NewValue");}});
1428 
1429         //----------------------------------------------------------------
1430         // Fiddle with environment sizes
1431         //----------------------------------------------------------------
1432         try {
1433             Map<String,String> environ = new ProcessBuilder().environment();
1434             int size = environ.size();
1435             checkSizes(environ, size);
1436 
1437             environ.put("UnLiKeLYeNVIROmtNam", "someVal");
1438             checkSizes(environ, size+1);
1439 
1440             // Check for environment independence
1441             new ProcessBuilder().environment().clear();
1442 
1443             environ.put("UnLiKeLYeNVIROmtNam", "someOtherVal");
1444             checkSizes(environ, size+1);
1445 
1446             environ.remove("UnLiKeLYeNVIROmtNam");
1447             checkSizes(environ, size);
1448 
1449             environ.clear();
1450             checkSizes(environ, 0);
1451 
1452             environ.clear();
1453             checkSizes(environ, 0);
1454 
1455             environ = new ProcessBuilder().environment();
1456             environ.keySet().clear();
1457             checkSizes(environ, 0);
1458 
1459             environ = new ProcessBuilder().environment();
1460             environ.entrySet().clear();
1461             checkSizes(environ, 0);
1462 
1463             environ = new ProcessBuilder().environment();
1464             environ.values().clear();
1465             checkSizes(environ, 0);
1466         } catch (Throwable t) { unexpected(t); }
1467 
1468         //----------------------------------------------------------------
1469         // Check that various map invariants hold
1470         //----------------------------------------------------------------
1471         checkMapSanity(new ProcessBuilder().environment());
1472         checkMapSanity(System.getenv());
1473         checkMapEquality(new ProcessBuilder().environment(),
1474                          new ProcessBuilder().environment());
1475 
1476 
1477         //----------------------------------------------------------------
1478         // Check effects on external "env" command.
1479         //----------------------------------------------------------------
1480         try {
1481             Set<String> env1 = new HashSet<String>
1482                 (Arrays.asList(nativeEnv((String[])null).split("\n")));
1483 
1484             ProcessBuilder pb = new ProcessBuilder();
1485             pb.environment().put("QwErTyUiOp","AsDfGhJk");
1486 
1487             Set<String> env2 = new HashSet<String>
1488                 (Arrays.asList(nativeEnv(pb).split("\n")));
1489 
1490             check(env2.size() == env1.size() + 1);
1491             env1.add("QwErTyUiOp=AsDfGhJk");
1492             check(env1.equals(env2));
1493         } catch (Throwable t) { unexpected(t); }
1494 
1495         //----------------------------------------------------------------
1496         // Test Runtime.exec(...envp...)
1497         // Check for sort order of environment variables on Windows.
1498         //----------------------------------------------------------------
1499         try {
1500             String systemRoot = "SystemRoot=" + System.getenv("SystemRoot");
1501             // '+' < 'A' < 'Z' < '_' < 'a' < 'z' < '~'
1502             String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=",
1503                             "+=+", "_=_", "~=~", systemRoot};
1504             String output = nativeEnv(envp);
1505             String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n";
1506             // On Windows, Java must keep the environment sorted.
1507             // Order is random on Unix, so this test does the sort.
1508             if (! Windows.is())
1509                 output = sortByLinesWindowsly(output);
1510             equal(output, expected);
1511         } catch (Throwable t) { unexpected(t); }
1512 
1513         //----------------------------------------------------------------
1514         // Test Runtime.exec(...envp...)
1515         // and check SystemRoot gets set automatically on Windows
1516         //----------------------------------------------------------------
1517         try {
1518             if (Windows.is()) {
1519                 String systemRoot = "SystemRoot=" + System.getenv("SystemRoot");
1520                 String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=",
1521                                 "+=+", "_=_", "~=~"};
1522                 String output = nativeEnv(envp);
1523                 String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n";
1524                 equal(output, expected);
1525             }
1526         } catch (Throwable t) { unexpected(t); }
1527 
1528         //----------------------------------------------------------------
1529         // System.getenv() must be consistent with System.getenv(String)
1530         //----------------------------------------------------------------
1531         try {
1532             for (Map.Entry<String,String> e : getenv().entrySet())
1533                 equal(getenv(e.getKey()), e.getValue());
1534         } catch (Throwable t) { unexpected(t); }
1535 
1536         //----------------------------------------------------------------
1537         // Fiddle with working directory in child
1538         //----------------------------------------------------------------
1539         try {
1540             String canonicalUserDir =
1541                 new File(System.getProperty("user.dir")).getCanonicalPath();
1542             String[] sdirs = new String[]
1543                 {".", "..", "/", "/bin",
1544                  "C:", "c:", "C:/", "c:\\", "\\", "\\bin",
1545                  "c:\\windows  ", "c:\\Program Files", "c:\\Program Files\\" };
1546             for (String sdir : sdirs) {
1547                 File dir = new File(sdir);
1548                 if (! (dir.isDirectory() && dir.exists()))
1549                     continue;
1550                 out.println("Testing directory " + dir);
1551                 //dir = new File(dir.getCanonicalPath());
1552 
1553                 ProcessBuilder pb = new ProcessBuilder();
1554                 equal(pb.directory(), null);
1555                 equal(pwdInChild(pb), canonicalUserDir);
1556 
1557                 pb.directory(dir);
1558                 equal(pb.directory(), dir);
1559                 equal(pwdInChild(pb), dir.getCanonicalPath());
1560 
1561                 pb.directory(null);
1562                 equal(pb.directory(), null);
1563                 equal(pwdInChild(pb), canonicalUserDir);
1564 
1565                 pb.directory(dir);
1566             }
1567         } catch (Throwable t) { unexpected(t); }
1568 
1569         //----------------------------------------------------------------
1570         // Working directory with Unicode in child
1571         //----------------------------------------------------------------
1572         try {
1573             if (UnicodeOS.is()) {
1574                 File dir = new File(System.getProperty("test.dir", "."),
1575                                     "ProcessBuilderDir\u4e00\u4e02");
1576                 try {
1577                     if (!dir.exists())
1578                         dir.mkdir();
1579                     out.println("Testing Unicode directory:" + dir);
1580                     ProcessBuilder pb = new ProcessBuilder();
1581                     pb.directory(dir);
1582                     equal(pwdInChild(pb), dir.getCanonicalPath());
1583                 } finally {
1584                     if (dir.exists())
1585                         dir.delete();
1586                 }
1587             }
1588         } catch (Throwable t) { unexpected(t); }
1589 
1590         //----------------------------------------------------------------
1591         // OOME in child allocating maximally sized array
1592         // Test for hotspot/jvmti bug 6850957
1593         //----------------------------------------------------------------
1594         try {
1595             List<String> list = new ArrayList<String>(javaChildArgs);
1596             list.add(1, String.format("-XX:OnOutOfMemoryError=%s -version",
1597                                       javaExe));
1598             list.add("ArrayOOME");
1599             ProcessResults r = run(new ProcessBuilder(list));
1600             check(r.out().contains("java.lang.OutOfMemoryError:"));
1601             check(r.out().contains(javaExe));
1602             check(r.err().contains(System.getProperty("java.version")));
1603             equal(r.exitValue(), 1);
1604         } catch (Throwable t) { unexpected(t); }
1605 
1606         //----------------------------------------------------------------
1607         // Windows has tricky semi-case-insensitive semantics
1608         //----------------------------------------------------------------
1609         if (Windows.is())
1610             try {
1611                 out.println("Running case insensitve variable tests");
1612                 for (String[] namePair :
1613                          new String[][]
1614                     { new String[]{"PATH","PaTh"},
1615                       new String[]{"home","HOME"},
1616                       new String[]{"SYSTEMROOT","SystemRoot"}}) {
1617                     check((getenv(namePair[0]) == null &&
1618                            getenv(namePair[1]) == null)
1619                           ||
1620                           getenv(namePair[0]).equals(getenv(namePair[1])),
1621                           "Windows environment variables are not case insensitive");
1622                 }
1623             } catch (Throwable t) { unexpected(t); }
1624 
1625         //----------------------------------------------------------------
1626         // Test proper Unicode child environment transfer
1627         //----------------------------------------------------------------
1628         if (UnicodeOS.is())
1629             try {
1630                 ProcessBuilder pb = new ProcessBuilder();
1631                 pb.environment().put("\u1234","\u5678");
1632                 pb.environment().remove("PATH");
1633                 equal(getenvInChild1234(pb), "\u5678");
1634             } catch (Throwable t) { unexpected(t); }
1635 
1636 
1637         //----------------------------------------------------------------
1638         // Test Runtime.exec(...envp...) with envstrings with initial `='
1639         //----------------------------------------------------------------
1640         try {
1641             List<String> childArgs = new ArrayList<String>(javaChildArgs);
1642             childArgs.add("System.getenv()");
1643             String[] cmdp = childArgs.toArray(new String[childArgs.size()]);
1644             String[] envp;
1645             String[] envpWin = {"=ExitValue=3", "=C:=\\", "SystemRoot="+systemRoot};
1646             String[] envpOth = {"=ExitValue=3", "=C:=\\"};
1647             if (Windows.is()) {
1648                 envp = envpWin;
1649             } else {
1650                 envp = envpOth;
1651             }
1652             Process p = Runtime.getRuntime().exec(cmdp, envp);
1653             String expected = Windows.is() ? "=C:=\\,SystemRoot="+systemRoot+",=ExitValue=3," : "=C:=\\,";
1654             String commandOutput = commandOutput(p);
1655             if (MacOSX.is()) {
1656                 commandOutput = removeMacExpectedVars(commandOutput);
1657             }
1658             equal(commandOutput, expected);
1659             if (Windows.is()) {
1660                 ProcessBuilder pb = new ProcessBuilder(childArgs);
1661                 pb.environment().clear();
1662                 pb.environment().put("SystemRoot", systemRoot);
1663                 pb.environment().put("=ExitValue", "3");
1664                 pb.environment().put("=C:", "\\");
1665                 equal(commandOutput(pb), expected);
1666             }
1667         } catch (Throwable t) { unexpected(t); }
1668 
1669         //----------------------------------------------------------------
1670         // Test Runtime.exec(...envp...) with envstrings without any `='
1671         //----------------------------------------------------------------
1672         try {
1673             String[] cmdp = {"echo"};
1674             String[] envp = {"Hello", "World"}; // Yuck!
1675             Process p = Runtime.getRuntime().exec(cmdp, envp);
1676             equal(commandOutput(p), "\n");
1677         } catch (Throwable t) { unexpected(t); }
1678 
1679         //----------------------------------------------------------------
1680         // Test Runtime.exec(...envp...) with envstrings containing NULs
1681         //----------------------------------------------------------------
1682         try {
1683             List<String> childArgs = new ArrayList<String>(javaChildArgs);
1684             childArgs.add("System.getenv()");
1685             String[] cmdp = childArgs.toArray(new String[childArgs.size()]);
1686             String[] envpWin = {"SystemRoot="+systemRoot, "LC_ALL=C\u0000\u0000", // Yuck!
1687                              "FO\u0000=B\u0000R"};
1688             String[] envpOth = {"LC_ALL=C\u0000\u0000", // Yuck!
1689                              "FO\u0000=B\u0000R"};
1690             String[] envp;
1691             if (Windows.is()) {
1692                 envp = envpWin;
1693             } else {
1694                 envp = envpOth;
1695             }
1696             System.out.println ("cmdp");
1697             for (int i=0; i<cmdp.length; i++) {
1698                 System.out.printf ("cmdp %d: %s\n", i, cmdp[i]);
1699             }
1700             System.out.println ("envp");
1701             for (int i=0; i<envp.length; i++) {
1702                 System.out.printf ("envp %d: %s\n", i, envp[i]);
1703             }
1704             Process p = Runtime.getRuntime().exec(cmdp, envp);
1705             String commandOutput = commandOutput(p);
1706             if (MacOSX.is()) {
1707                 commandOutput = removeMacExpectedVars(commandOutput);
1708             }
1709             check(commandOutput.equals(Windows.is()
1710                     ? "SystemRoot="+systemRoot+",LC_ALL=C,"
1711                     : "LC_ALL=C,"),
1712                   "Incorrect handling of envstrings containing NULs");
1713         } catch (Throwable t) { unexpected(t); }
1714 
1715         //----------------------------------------------------------------
1716         // Test the redirectErrorStream property
1717         //----------------------------------------------------------------
1718         try {
1719             ProcessBuilder pb = new ProcessBuilder();
1720             equal(pb.redirectErrorStream(), false);
1721             equal(pb.redirectErrorStream(true), pb);
1722             equal(pb.redirectErrorStream(), true);
1723             equal(pb.redirectErrorStream(false), pb);
1724             equal(pb.redirectErrorStream(), false);
1725         } catch (Throwable t) { unexpected(t); }
1726 
1727         try {
1728             List<String> childArgs = new ArrayList<String>(javaChildArgs);
1729             childArgs.add("OutErr");
1730             ProcessBuilder pb = new ProcessBuilder(childArgs);
1731             {
1732                 ProcessResults r = run(pb);
1733                 equal(r.out(), "outout");
1734                 equal(r.err(), "errerr");
1735             }
1736             {
1737                 pb.redirectErrorStream(true);
1738                 ProcessResults r = run(pb);
1739                 equal(r.out(), "outerrouterr");
1740                 equal(r.err(), "");
1741             }
1742         } catch (Throwable t) { unexpected(t); }
1743 
1744         if (Unix.is()) {
1745             //----------------------------------------------------------------
1746             // We can find true and false when PATH is null
1747             //----------------------------------------------------------------
1748             try {
1749                 List<String> childArgs = new ArrayList<String>(javaChildArgs);
1750                 childArgs.add("null PATH");
1751                 ProcessBuilder pb = new ProcessBuilder(childArgs);
1752                 pb.environment().remove("PATH");
1753                 ProcessResults r = run(pb);
1754                 equal(r.out(), "");
1755                 equal(r.err(), "");
1756                 equal(r.exitValue(), 0);
1757             } catch (Throwable t) { unexpected(t); }
1758 
1759             //----------------------------------------------------------------
1760             // PATH search algorithm on Unix
1761             //----------------------------------------------------------------
1762             try {
1763                 List<String> childArgs = new ArrayList<String>(javaChildArgs);
1764                 childArgs.add("PATH search algorithm");
1765                 ProcessBuilder pb = new ProcessBuilder(childArgs);
1766                 pb.environment().put("PATH", "dir1:dir2:");
1767                 ProcessResults r = run(pb);
1768                 equal(r.out(), "");
1769                 equal(r.err(), "");
1770                 equal(r.exitValue(), True.exitValue());
1771             } catch (Throwable t) { unexpected(t); }
1772 
1773             //----------------------------------------------------------------
1774             // Parent's, not child's PATH is used
1775             //----------------------------------------------------------------
1776             try {
1777                 new File("suBdiR").mkdirs();
1778                 copy("/bin/true", "suBdiR/unliKely");
1779                 final ProcessBuilder pb =
1780                     new ProcessBuilder(new String[]{"unliKely"});
1781                 pb.environment().put("PATH", "suBdiR");
1782                 THROWS(IOException.class,
1783                        new Fun() {void f() throws Throwable {pb.start();}});
1784             } catch (Throwable t) { unexpected(t);
1785             } finally {
1786                 new File("suBdiR/unliKely").delete();
1787                 new File("suBdiR").delete();
1788             }
1789         }
1790 
1791         //----------------------------------------------------------------
1792         // Attempt to start bogus program ""
1793         //----------------------------------------------------------------
1794         try {
1795             new ProcessBuilder("").start();
1796             fail("Expected IOException not thrown");
1797         } catch (IOException e) {
1798             String m = e.getMessage();
1799             if (EnglishUnix.is() &&
1800                 ! matches(m, "No such file or directory"))
1801                 unexpected(e);
1802         } catch (Throwable t) { unexpected(t); }
1803 
1804         //----------------------------------------------------------------
1805         // Check that attempt to execute program name with funny
1806         // characters throws an exception containing those characters.
1807         //----------------------------------------------------------------
1808         for (String programName : new String[] {"\u00f0", "\u01f0"})
1809             try {
1810                 new ProcessBuilder(programName).start();
1811                 fail("Expected IOException not thrown");
1812             } catch (IOException e) {
1813                 String m = e.getMessage();
1814                 Pattern p = Pattern.compile(programName);
1815                 if (! matches(m, programName)
1816                     || (EnglishUnix.is()
1817                         && ! matches(m, "No such file or directory")))
1818                     unexpected(e);
1819             } catch (Throwable t) { unexpected(t); }
1820 
1821         //----------------------------------------------------------------
1822         // Attempt to start process in nonexistent directory fails.
1823         //----------------------------------------------------------------
1824         try {
1825             new ProcessBuilder("echo")
1826                 .directory(new File("UnLiKeLY"))
1827                 .start();
1828             fail("Expected IOException not thrown");
1829         } catch (IOException e) {
1830             String m = e.getMessage();
1831             if (! matches(m, "in directory")
1832                 || (EnglishUnix.is() &&
1833                     ! matches(m, "No such file or directory")))
1834                 unexpected(e);
1835         } catch (Throwable t) { unexpected(t); }
1836 
1837         //----------------------------------------------------------------
1838         // Attempt to write 4095 bytes to the pipe buffer without a
1839         // reader to drain it would deadlock, if not for the fact that
1840         // interprocess pipe buffers are at least 4096 bytes.
1841         //
1842         // Also, check that available reports all the bytes expected
1843         // in the pipe buffer, and that I/O operations do the expected
1844         // things.
1845         //----------------------------------------------------------------
1846         try {
1847             List<String> childArgs = new ArrayList<String>(javaChildArgs);
1848             childArgs.add("print4095");
1849             final int SIZE = 4095;
1850             final Process p = new ProcessBuilder(childArgs).start();
1851             print4095(p.getOutputStream(), (byte) '!'); // Might hang!
1852             p.waitFor();                                // Might hang!
1853             equal(SIZE, p.getInputStream().available());
1854             equal(SIZE, p.getErrorStream().available());
1855             THROWS(IOException.class,
1856                    new Fun(){void f() throws IOException {
1857                        p.getOutputStream().write((byte) '!');
1858                        p.getOutputStream().flush();
1859                        }});
1860 
1861             final byte[] bytes = new byte[SIZE + 1];
1862             equal(SIZE, p.getInputStream().read(bytes));
1863             for (int i = 0; i < SIZE; i++)
1864                 equal((byte) '!', bytes[i]);
1865             equal((byte) 0, bytes[SIZE]);
1866 
1867             equal(SIZE, p.getErrorStream().read(bytes));
1868             for (int i = 0; i < SIZE; i++)
1869                 equal((byte) 'E', bytes[i]);
1870             equal((byte) 0, bytes[SIZE]);
1871 
1872             equal(0, p.getInputStream().available());
1873             equal(0, p.getErrorStream().available());
1874             equal(-1, p.getErrorStream().read());
1875             equal(-1, p.getInputStream().read());
1876 
1877             equal(p.exitValue(), 5);
1878 
1879             p.getInputStream().close();
1880             p.getErrorStream().close();
1881             p.getOutputStream().close();
1882 
1883             InputStream[] streams = { p.getInputStream(), p.getErrorStream() };
1884             for (final InputStream in : streams) {
1885                 Fun[] ops = {
1886                     new Fun(){void f() throws IOException {
1887                         in.read(); }},
1888                     new Fun(){void f() throws IOException {
1889                         in.read(bytes); }},
1890                     new Fun(){void f() throws IOException {
1891                         in.available(); }}
1892                 };
1893                 for (Fun op : ops) {
1894                     try {
1895                         op.f();
1896                         fail();
1897                     } catch (IOException expected) {
1898                         check(expected.getMessage()
1899                               .matches("[Ss]tream [Cc]losed"));
1900                     }
1901                 }
1902             }
1903         } catch (Throwable t) { unexpected(t); }
1904 
1905         //----------------------------------------------------------------
1906         // Check that reads which are pending when Process.destroy is
1907         // called, get EOF, not IOException("Stream closed").
1908         //----------------------------------------------------------------
1909         try {
1910             final int cases = 4;
1911             for (int i = 0; i < cases; i++) {
1912                 final int action = i;
1913                 List<String> childArgs = new ArrayList<String>(javaChildArgs);
1914                 childArgs.add("sleep");
1915                 final byte[] bytes = new byte[10];
1916                 final Process p = new ProcessBuilder(childArgs).start();
1917                 final CountDownLatch latch = new CountDownLatch(1);
1918                 final InputStream s;
1919                 switch (action & 0x1) {
1920                     case 0: s = p.getInputStream(); break;
1921                     case 1: s = p.getErrorStream(); break;
1922                     default: throw new Error();
1923                 }
1924                 final Thread thread = new Thread() {
1925                     public void run() {
1926                         try {
1927                             int r;
1928                             latch.countDown();
1929                             switch (action & 0x2) {
1930                                 case 0: r = s.read(); break;
1931                                 case 2: r = s.read(bytes); break;
1932                                 default: throw new Error();
1933                             }
1934                             equal(-1, r);
1935                         } catch (Throwable t) { unexpected(t); }}};
1936 
1937                 thread.start();
1938                 latch.await();
1939                 Thread.sleep(10);
1940 
1941                 String os = System.getProperty("os.name");
1942                 if (os.equalsIgnoreCase("Solaris") ||
1943                     os.equalsIgnoreCase("SunOS"))
1944                 {
1945                     final Object deferred;
1946                     Class<?> c = s.getClass();
1947                     if (c.getName().equals(
1948                         "java.lang.UNIXProcess$DeferredCloseInputStream"))
1949                     {
1950                         deferred = s;
1951                     } else {
1952                         Field deferredField = p.getClass().
1953                             getDeclaredField("stdout_inner_stream");
1954                         deferredField.setAccessible(true);
1955                         deferred = deferredField.get(p);
1956                     }
1957                     Field useCountField = deferred.getClass().
1958                         getDeclaredField("useCount");
1959                     useCountField.setAccessible(true);
1960 
1961                     while (useCountField.getInt(deferred) <= 0) {
1962                         Thread.yield();
1963                     }
1964                 } else if (s instanceof BufferedInputStream) {
1965                     Field f = Unsafe.class.getDeclaredField("theUnsafe");
1966                     f.setAccessible(true);
1967                     Unsafe unsafe = (Unsafe)f.get(null);
1968 
1969                     while (unsafe.tryMonitorEnter(s)) {
1970                         unsafe.monitorExit(s);
1971                         Thread.sleep(1);
1972                     }
1973                 }
1974                 p.destroy();
1975                 thread.join();
1976             }
1977         } catch (Throwable t) { unexpected(t); }
1978 
1979         //----------------------------------------------------------------
1980         // Check that subprocesses which create subprocesses of their
1981         // own do not cause parent to hang waiting for file
1982         // descriptors to be closed.
1983         //----------------------------------------------------------------
1984         try {
1985             if (Unix.is()
1986                 && new File("/bin/bash").exists()
1987                 && new File("/bin/sleep").exists()) {
1988                 final String[] cmd = { "/bin/bash", "-c", "(/bin/sleep 6666)" };
1989                 final ProcessBuilder pb = new ProcessBuilder(cmd);
1990                 final Process p = pb.start();
1991                 final InputStream stdout = p.getInputStream();
1992                 final InputStream stderr = p.getErrorStream();
1993                 final OutputStream stdin = p.getOutputStream();
1994                 final Thread reader = new Thread() {
1995                     public void run() {
1996                         try { stdout.read(); }
1997                         catch (IOException e) {
1998                             // Check that reader failed because stream was
1999                             // asynchronously closed.
2000                             // e.printStackTrace();
2001                             if (EnglishUnix.is() &&
2002                                 ! (e.getMessage().matches(".*Bad file.*")))
2003                                 unexpected(e);
2004                         }
2005                         catch (Throwable t) { unexpected(t); }}};
2006                 reader.setDaemon(true);
2007                 reader.start();
2008                 Thread.sleep(100);
2009                 p.destroy();
2010                 // Subprocess is now dead, but file descriptors remain open.
2011                 check(p.waitFor() != 0);
2012                 check(p.exitValue() != 0);
2013                 stdout.close();
2014                 stderr.close();
2015                 stdin.close();
2016                 //----------------------------------------------------------
2017                 // There remain unsolved issues with asynchronous close.
2018                 // Here's a highly non-portable experiment to demonstrate:
2019                 //----------------------------------------------------------
2020                 if (Boolean.getBoolean("wakeupJeff!")) {
2021                     System.out.println("wakeupJeff!");
2022                     // Initialize signal handler for INTERRUPT_SIGNAL.
2023                     new FileInputStream("/bin/sleep").getChannel().close();
2024                     // Send INTERRUPT_SIGNAL to every thread in this java.
2025                     String[] wakeupJeff = {
2026                         "/bin/bash", "-c",
2027                         "/bin/ps --noheaders -Lfp $PPID | " +
2028                         "/usr/bin/perl -nale 'print $F[3]' | " +
2029                         // INTERRUPT_SIGNAL == 62 on my machine du jour.
2030                         "/usr/bin/xargs kill -62"
2031                     };
2032                     new ProcessBuilder(wakeupJeff).start().waitFor();
2033                     // If wakeupJeff worked, reader probably got EBADF.
2034                     reader.join();
2035                 }
2036             }
2037         } catch (Throwable t) { unexpected(t); }
2038 
2039         //----------------------------------------------------------------
2040         // Attempt to start process with insufficient permissions fails.
2041         //----------------------------------------------------------------
2042         try {
2043             new File("emptyCommand").delete();
2044             new FileOutputStream("emptyCommand").close();
2045             new File("emptyCommand").setExecutable(false);
2046             new ProcessBuilder("./emptyCommand").start();
2047             fail("Expected IOException not thrown");
2048         } catch (IOException e) {
2049             new File("./emptyCommand").delete();
2050             String m = e.getMessage();
2051             if (EnglishUnix.is() &&
2052                 ! matches(m, "Permission denied"))
2053                 unexpected(e);
2054         } catch (Throwable t) { unexpected(t); }
2055 
2056         new File("emptyCommand").delete();
2057 
2058         //----------------------------------------------------------------
2059         // Check for correct security permission behavior
2060         //----------------------------------------------------------------
2061         final Policy policy = new Policy();
2062         Policy.setPolicy(policy);
2063         System.setSecurityManager(new SecurityManager());
2064 
2065         try {
2066             // No permissions required to CREATE a ProcessBuilder
2067             policy.setPermissions(/* Nothing */);
2068             new ProcessBuilder("env").directory(null).directory();
2069             new ProcessBuilder("env").directory(new File("dir")).directory();
2070             new ProcessBuilder("env").command("??").command();
2071         } catch (Throwable t) { unexpected(t); }
2072 
2073         THROWS(SecurityException.class,
2074             new Fun() { void f() throws IOException {
2075                 policy.setPermissions(/* Nothing */);
2076                 System.getenv("foo");}},
2077             new Fun() { void f() throws IOException {
2078                 policy.setPermissions(/* Nothing */);
2079                 System.getenv();}},
2080             new Fun() { void f() throws IOException {
2081                 policy.setPermissions(/* Nothing */);
2082                 new ProcessBuilder("echo").start();}},
2083             new Fun() { void f() throws IOException {
2084                 policy.setPermissions(/* Nothing */);
2085                 Runtime.getRuntime().exec("echo");}},
2086             new Fun() { void f() throws IOException {
2087                 policy.setPermissions(new RuntimePermission("getenv.bar"));
2088                 System.getenv("foo");}});
2089 
2090         try {
2091             policy.setPermissions(new RuntimePermission("getenv.foo"));
2092             System.getenv("foo");
2093 
2094             policy.setPermissions(new RuntimePermission("getenv.*"));
2095             System.getenv("foo");
2096             System.getenv();
2097             new ProcessBuilder().environment();
2098         } catch (Throwable t) { unexpected(t); }
2099 
2100 
2101         final Permission execPermission
2102             = new FilePermission("<<ALL FILES>>", "execute");
2103 
2104         THROWS(SecurityException.class,
2105             new Fun() { void f() throws IOException {
2106                 // environment permission by itself insufficient
2107                 policy.setPermissions(new RuntimePermission("getenv.*"));
2108                 ProcessBuilder pb = new ProcessBuilder("env");
2109                 pb.environment().put("foo","bar");
2110                 pb.start();}},
2111             new Fun() { void f() throws IOException {
2112                  // exec permission by itself insufficient
2113                  policy.setPermissions(execPermission);
2114                  ProcessBuilder pb = new ProcessBuilder("env");
2115                  pb.environment().put("foo","bar");
2116                  pb.start();}});
2117 
2118         try {
2119             // Both permissions? OK.
2120             policy.setPermissions(new RuntimePermission("getenv.*"),
2121                                   execPermission);
2122             ProcessBuilder pb = new ProcessBuilder("env");
2123             pb.environment().put("foo","bar");
2124             Process p = pb.start();
2125             closeStreams(p);
2126         } catch (IOException e) { // OK
2127         } catch (Throwable t) { unexpected(t); }
2128 
2129         try {
2130             // Don't need environment permission unless READING environment
2131             policy.setPermissions(execPermission);
2132             Runtime.getRuntime().exec("env", new String[]{});
2133         } catch (IOException e) { // OK
2134         } catch (Throwable t) { unexpected(t); }
2135 
2136         try {
2137             // Don't need environment permission unless READING environment
2138             policy.setPermissions(execPermission);
2139             new ProcessBuilder("env").start();
2140         } catch (IOException e) { // OK
2141         } catch (Throwable t) { unexpected(t); }
2142 
2143         // Restore "normal" state without a security manager
2144         policy.setPermissions(new RuntimePermission("setSecurityManager"));
2145         System.setSecurityManager(null);
2146 
2147     }
2148 
2149     static void closeStreams(Process p) {
2150         try {
2151             p.getOutputStream().close();
2152             p.getInputStream().close();
2153             p.getErrorStream().close();
2154         } catch (Throwable t) { unexpected(t); }
2155     }
2156 
2157     //----------------------------------------------------------------
2158     // A Policy class designed to make permissions fiddling very easy.
2159     //----------------------------------------------------------------
2160     private static class Policy extends java.security.Policy {
2161         private Permissions perms;
2162 
2163         public void setPermissions(Permission...permissions) {
2164             perms = new Permissions();
2165             for (Permission permission : permissions)
2166                 perms.add(permission);
2167         }
2168 
2169         public Policy() { setPermissions(/* Nothing */); }
2170 
2171         public PermissionCollection getPermissions(CodeSource cs) {
2172             return perms;
2173         }
2174 
2175         public PermissionCollection getPermissions(ProtectionDomain pd) {
2176             return perms;
2177         }
2178 
2179         public boolean implies(ProtectionDomain pd, Permission p) {
2180             return perms.implies(p);
2181         }
2182 
2183         public void refresh() {}
2184     }
2185 
2186     private static class StreamAccumulator extends Thread {
2187         private final InputStream is;
2188         private final StringBuilder sb = new StringBuilder();
2189         private Throwable throwable = null;
2190 
2191         public String result () throws Throwable {
2192             if (throwable != null)
2193                 throw throwable;
2194             return sb.toString();
2195         }
2196 
2197         StreamAccumulator (InputStream is) {
2198             this.is = is;
2199         }
2200 
2201         public void run() {
2202             try {
2203                 Reader r = new InputStreamReader(is);
2204                 char[] buf = new char[4096];
2205                 int n;
2206                 while ((n = r.read(buf)) > 0) {
2207                     sb.append(buf,0,n);
2208                 }
2209             } catch (Throwable t) {
2210                 throwable = t;
2211             } finally {
2212                 try { is.close(); }
2213                 catch (Throwable t) { throwable = t; }
2214             }
2215         }
2216     }
2217 
2218     static ProcessResults run(ProcessBuilder pb) {
2219         try {
2220             return run(pb.start());
2221         } catch (Throwable t) { unexpected(t); return null; }
2222     }
2223 
2224     private static ProcessResults run(Process p) {
2225         Throwable throwable = null;
2226         int exitValue = -1;
2227         String out = "";
2228         String err = "";
2229 
2230         StreamAccumulator outAccumulator =
2231             new StreamAccumulator(p.getInputStream());
2232         StreamAccumulator errAccumulator =
2233             new StreamAccumulator(p.getErrorStream());
2234 
2235         try {
2236             outAccumulator.start();
2237             errAccumulator.start();
2238 
2239             exitValue = p.waitFor();
2240 
2241             outAccumulator.join();
2242             errAccumulator.join();
2243 
2244             out = outAccumulator.result();
2245             err = errAccumulator.result();
2246         } catch (Throwable t) {
2247             throwable = t;
2248         }
2249 
2250         return new ProcessResults(out, err, exitValue, throwable);
2251     }
2252 
2253     //----------------------------------------------------------------
2254     // Results of a command
2255     //----------------------------------------------------------------
2256     private static class ProcessResults {
2257         private final String out;
2258         private final String err;
2259         private final int exitValue;
2260         private final Throwable throwable;
2261 
2262         public ProcessResults(String out,
2263                               String err,
2264                               int exitValue,
2265                               Throwable throwable) {
2266             this.out = out;
2267             this.err = err;
2268             this.exitValue = exitValue;
2269             this.throwable = throwable;
2270         }
2271 
2272         public String out()          { return out; }
2273         public String err()          { return err; }
2274         public int exitValue()       { return exitValue; }
2275         public Throwable throwable() { return throwable; }
2276 
2277         public String toString() {
2278             StringBuilder sb = new StringBuilder();
2279             sb.append("<STDOUT>\n" + out() + "</STDOUT>\n")
2280                 .append("<STDERR>\n" + err() + "</STDERR>\n")
2281                 .append("exitValue = " + exitValue + "\n");
2282             if (throwable != null)
2283                 sb.append(throwable.getStackTrace());
2284             return sb.toString();
2285         }
2286     }
2287 
2288     //--------------------- Infrastructure ---------------------------
2289     static volatile int passed = 0, failed = 0;
2290     static void pass() {passed++;}
2291     static void fail() {failed++; Thread.dumpStack();}
2292     static void fail(String msg) {System.out.println(msg); fail();}
2293     static void unexpected(Throwable t) {failed++; t.printStackTrace();}
2294     static void check(boolean cond) {if (cond) pass(); else fail();}
2295     static void check(boolean cond, String m) {if (cond) pass(); else fail(m);}
2296     static void equal(Object x, Object y) {
2297         if (x == null ? y == null : x.equals(y)) pass();
2298         else fail(x + " not equal to " + y);}
2299 
2300     public static void main(String[] args) throws Throwable {
2301         try {realMain(args);} catch (Throwable t) {unexpected(t);}
2302         System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);
2303         if (failed > 0) throw new AssertionError("Some tests failed");}
2304     private static abstract class Fun {abstract void f() throws Throwable;}
2305     static void THROWS(Class<? extends Throwable> k, Fun... fs) {
2306         for (Fun f : fs)
2307             try { f.f(); fail("Expected " + k.getName() + " not thrown"); }
2308             catch (Throwable t) {
2309                 if (k.isAssignableFrom(t.getClass())) pass();
2310                 else unexpected(t);}}
2311 }