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