6850720: (process) Use clone(CLONE_VM), not fork, on Linux to avoid swap exhaustion
Summary: Use clone(CLONE_VM) on Linux; Reluctantly implement execvpe.
Reviewed-by: michaelm
diff --git a/test/java/lang/ProcessBuilder/Basic.java b/test/java/lang/ProcessBuilder/Basic.java
index d5ac05f..745fab4 100644
--- a/test/java/lang/ProcessBuilder/Basic.java
+++ b/test/java/lang/ProcessBuilder/Basic.java
@@ -257,6 +257,18 @@
         s.write(bytes);         // Might hang!
     }
 
+    static void checkPermissionDenied(ProcessBuilder pb) {
+        try {
+            pb.start();
+            fail("Expected IOException not thrown");
+        } catch (IOException e) {
+            String m = e.getMessage();
+            if (EnglishUnix.is() &&
+                ! matches(m, "Permission denied"))
+                unexpected(e);
+        } catch (Throwable t) { unexpected(t); }
+    }
+
     public static class JavaChild {
         public static void main(String args[]) throws Throwable {
             String action = args[0];
@@ -317,12 +329,10 @@
                 for (final ProcessBuilder pb :
                          new ProcessBuilder[] {pb1, pb2}) {
                     pb.command("true");
-                    r = run(pb.start());
-                    equal(r.exitValue(), True.exitValue());
+                    equal(run(pb).exitValue(), True.exitValue());
 
                     pb.command("false");
-                    r = run(pb.start());
-                    equal(r.exitValue(), False.exitValue());
+                    equal(run(pb).exitValue(), False.exitValue());
                 }
 
                 if (failed != 0) throw new Error("null PATH");
@@ -367,31 +377,82 @@
                         // Can't execute a directory -- permission denied
                         // Report EACCES errno
                         new File("dir1/prog").mkdirs();
-                        try {
-                            pb.start();
-                            fail("Expected IOException not thrown");
-                        } catch (IOException e) {
-                            String m = e.getMessage();
-                            if (EnglishUnix.is() &&
-                                ! matches(m, "Permission denied"))
-                                unexpected(e);
-                        } catch (Throwable t) { unexpected(t); }
+                        checkPermissionDenied(pb);
 
                         // continue searching if EACCES
                         copy("/bin/true", "dir2/prog");
-                        equal(run(pb.start()).exitValue(), True.exitValue());
+                        equal(run(pb).exitValue(), True.exitValue());
                         new File("dir1/prog").delete();
                         new File("dir2/prog").delete();
 
                         new File("dir2/prog").mkdirs();
                         copy("/bin/true", "dir1/prog");
-                        equal(run(pb.start()).exitValue(), True.exitValue());
+                        equal(run(pb).exitValue(), True.exitValue());
 
-                        // Check empty PATH component means current directory
+                        // Check empty PATH component means current directory.
+                        //
+                        // While we're here, let's test different kinds of
+                        // Unix executables, and PATH vs explicit searching.
                         new File("dir1/prog").delete();
                         new File("dir2/prog").delete();
-                        copy("/bin/true", "./prog");
-                        equal(run(pb.start()).exitValue(), True.exitValue());
+                        for (String[] command :
+                                 new String[][] {
+                                     new String[] {"./prog"},
+                                     cmd}) {
+                            pb.command(command);
+                            File prog = new File("./prog");
+                            // "Normal" binaries
+                            copy("/bin/true", "./prog");
+                            equal(run(pb).exitValue(),
+                                  True.exitValue());
+                            copy("/bin/false", "./prog");
+                            equal(run(pb).exitValue(),
+                                  False.exitValue());
+                            prog.delete();
+                            // Interpreter scripts with #!
+                            setFileContents(prog, "#!/bin/true\n");
+                            prog.setExecutable(true);
+                            equal(run(pb).exitValue(),
+                                  True.exitValue());
+                            prog.delete();
+                            setFileContents(prog, "#!/bin/false\n");
+                            prog.setExecutable(true);
+                            equal(run(pb).exitValue(),
+                                  False.exitValue());
+                            // Traditional shell scripts without #!
+                            setFileContents(prog, "exec /bin/true\n");
+                            prog.setExecutable(true);
+                            equal(run(pb).exitValue(),
+                                  True.exitValue());
+                            prog.delete();
+                            setFileContents(prog, "exec /bin/false\n");
+                            prog.setExecutable(true);
+                            equal(run(pb).exitValue(),
+                                  False.exitValue());
+                            prog.delete();
+                        }
+
+                        // Test Unix interpreter scripts
+                        File dir1Prog = new File("dir1/prog");
+                        dir1Prog.delete();
+                        pb.command(new String[] {"prog", "world"});
+                        setFileContents(dir1Prog, "#!/bin/echo hello\n");
+                        checkPermissionDenied(pb);
+                        dir1Prog.setExecutable(true);
+                        equal(run(pb).out(), "hello dir1/prog world\n");
+                        equal(run(pb).exitValue(), True.exitValue());
+                        dir1Prog.delete();
+                        pb.command(cmd);
+
+                        // Test traditional shell scripts without #!
+                        setFileContents(dir1Prog, "/bin/echo \"$@\"\n");
+                        pb.command(new String[] {"prog", "hello", "world"});
+                        checkPermissionDenied(pb);
+                        dir1Prog.setExecutable(true);
+                        equal(run(pb).out(), "hello world\n");
+                        equal(run(pb).exitValue(), True.exitValue());
+                        dir1Prog.delete();
+                        pb.command(cmd);
 
                         // If prog found on both parent and child's PATH,
                         // parent's is used.
@@ -402,10 +463,10 @@
                         copy("/bin/true", "dir1/prog");
                         copy("/bin/false", "dir3/prog");
                         pb.environment().put("PATH","dir3");
-                        equal(run(pb.start()).exitValue(), True.exitValue());
+                        equal(run(pb).exitValue(), True.exitValue());
                         copy("/bin/true", "dir3/prog");
                         copy("/bin/false", "dir1/prog");
-                        equal(run(pb.start()).exitValue(), False.exitValue());
+                        equal(run(pb).exitValue(), False.exitValue());
 
                     } finally {
                         // cleanup
@@ -1503,21 +1564,19 @@
             childArgs.add("OutErr");
             ProcessBuilder pb = new ProcessBuilder(childArgs);
             {
-                ProcessResults r = run(pb.start());
+                ProcessResults r = run(pb);
                 equal(r.out(), "outout");
                 equal(r.err(), "errerr");
             }
             {
                 pb.redirectErrorStream(true);
-                ProcessResults r = run(pb.start());
+                ProcessResults r = run(pb);
                 equal(r.out(), "outerrouterr");
                 equal(r.err(), "");
             }
         } catch (Throwable t) { unexpected(t); }
 
-        if (! Windows.is() &&
-            new File("/bin/true").exists() &&
-            new File("/bin/false").exists()) {
+        if (Unix.is()) {
             //----------------------------------------------------------------
             // We can find true and false when PATH is null
             //----------------------------------------------------------------
@@ -1526,7 +1585,7 @@
                 childArgs.add("null PATH");
                 ProcessBuilder pb = new ProcessBuilder(childArgs);
                 pb.environment().remove("PATH");
-                ProcessResults r = run(pb.start());
+                ProcessResults r = run(pb);
                 equal(r.out(), "");
                 equal(r.err(), "");
                 equal(r.exitValue(), 0);
@@ -1540,7 +1599,7 @@
                 childArgs.add("PATH search algorithm");
                 ProcessBuilder pb = new ProcessBuilder(childArgs);
                 pb.environment().put("PATH", "dir1:dir2:");
-                ProcessResults r = run(pb.start());
+                ProcessResults r = run(pb);
                 equal(r.out(), "");
                 equal(r.err(), "");
                 equal(r.exitValue(), True.exitValue());