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