undefect. CWE-407 — 63 sites patched across 27 ecosystems

Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,321 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8282008
* @requires (os.family == "windows")
* @run main/othervm ArgCheck
* @summary Check invocation of exe and non-exe programs using ProcessBuilder
* and arguments with spaces, backslashes, and simple quoting.
*/
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Class to check invocation of java, .cmd, and vbs scripts with arguments and various quote cases.
* Can be run standalone to compare results with other Java versions.
*/
public class ArgCheck {
private static final Path SRC_DIR = Paths.get(System.getProperty("test.src", "."));
private static final Path WORK_DIR = Paths.get(System.getProperty("user.dir", "."));
private static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
private static final String ECHO_CMD_PATH = WORK_DIR.resolve("EchoArguments.cmd").toString();
private static final String ECHO_VBS_PATH = WORK_DIR.resolve("EchoArguments.vbs").toString();
// Test argument containing both a space and a trailing backslash
// Depending on the mode the final backslash may act as an escape that may turn an added quote to a literal quote
private static final String SPACE_AND_BACKSLASH = "SPACE AND BACKSLASH\\";
private static final char DOUBLE_QUOTE = '"';
private static final char BACKSLASH = '\\';
private static final String AMBIGUOUS_PROP_NAME = "jdk.lang.Process.allowAmbiguousCommands";
private static final String AMBIGUOUS_PROP_VALUE = System.getProperty(AMBIGUOUS_PROP_NAME);
private static final Boolean AMBIGUOUS_PROP_BOOLEAN = AMBIGUOUS_PROP_VALUE == null ? null :
Boolean.valueOf(!"false".equals(AMBIGUOUS_PROP_VALUE));
private static final List<String> ECHO_JAVA_ARGS = Arrays.asList("java", "-classpath", TEST_CLASSES.toString(), "ArgCheck");
private static final List<String> ECHO_CMD_ARGS = Arrays.asList(ECHO_CMD_PATH);
private static final List<String> ECHO_VBS_ARGS = Arrays.asList("CScript", "/b", ECHO_VBS_PATH);
/**
* If zero arguments are supplied, run the test cases, by launching each as a child process.
* If there are arguments, then this is a child Java process that prints each argument to stdout.
* The test can be run manually with -Djdk.lang.Process.allowAmbiguousCommands={"true", "false", ""}
* to run a matching subset of the tests.
*/
public static void main(String[] args) throws IOException {
if (args.length > 0) {
// Echo supplied arguments and exit
for (String arg : args)
System.out.println(arg);
return;
}
System.out.println("Java Version: " + System.getProperty("java.version"));
createFiles();
int errors = 0;
int success = 0;
int skipped = 0;
for (CMD cmd : CASES) {
// If System property jdk.lang.process.allowAmbiguousCommands matches the case, test it
// If undefined, test them all
if (AMBIGUOUS_PROP_BOOLEAN == null ||
AMBIGUOUS_PROP_BOOLEAN.booleanValue() == cmd.allowAmbiguous) {
try {
testCommand(cmd);
success++;
} catch (Exception ex) {
ex.printStackTrace();
errors++;
}
} else {
// skip unmatched cases
skipped++;
}
}
if (skipped > 0) {
System.out.printf("%d cases skipped, they did not match the tests with jdk.lang.Process.allowAmbiguousCommands: %s%n",
skipped, AMBIGUOUS_PROP_BOOLEAN);
}
System.out.printf("\nSuccess: %d, errors: %d%n", success, errors);
if (errors > 0) {
throw new RuntimeException("Errors: " + errors);
}
}
/**
* A CMD holds the parameters and the expected result of invoking a process with the parameters.
*/
static class CMD {
/**
* Construct a test case.
* @param allowAmbiguous true/false to set property jdk.lang.Process.allowAmbiguousCommands
* @param command list of command parameters to invoke the executable or script
* @param arguments list of arguments (appended to the command)
* @param expected expected lines of output from invoked command
*/
CMD(boolean allowAmbiguous, List<String> command, List<String> arguments, List<String> expected) {
this.allowAmbiguous = allowAmbiguous;
this.command = command;
this.arguments = arguments;
this.expected = expected;
}
final boolean allowAmbiguous;
final List<String> command;
final List<String> arguments;
final List<String> expected;
}
/**
* List of cases with the command, arguments, allowAmbiguous setting, and the expected results
*/
static final List<CMD> CASES = Arrays.asList(
// allowAmbiguousCommands = false, without application supplied double-quotes.
// The space in the argument requires it to be quoted, the final backslash
// must not be allowed to turn the quote that is added into a literal
// instead of closing the quote.
new CMD(false,
ECHO_JAVA_ARGS,
Arrays.asList(SPACE_AND_BACKSLASH, "ARG_1"),
Arrays.asList(SPACE_AND_BACKSLASH, "ARG_1")),
new CMD(false,
ECHO_CMD_ARGS,
Arrays.asList(SPACE_AND_BACKSLASH, "ARG_2"),
Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_2")),
new CMD(false,
ECHO_VBS_ARGS,
Arrays.asList(SPACE_AND_BACKSLASH, "ARG_3"),
Arrays.asList(SPACE_AND_BACKSLASH + BACKSLASH, "ARG_3")),
// allowAmbiguousCommands = false, WITH application supplied double-quotes around the argument
// The argument has surrounding quotes so does not need further quoting.
// However, for exe commands, the final backslash must not be allowed to turn the quote
// into a literal instead of closing the quote.
new CMD(false,
ECHO_JAVA_ARGS,
Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_11"),
Arrays.asList(SPACE_AND_BACKSLASH, "ARG_11")),
new CMD(false,
ECHO_CMD_ARGS,
Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_12"),
Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_12")),
new CMD(false,
ECHO_VBS_ARGS,
Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_13"),
Arrays.asList(SPACE_AND_BACKSLASH + BACKSLASH, "ARG_13")),
// Legacy mode tests; allowAmbiguousCommands = true; no application supplied quotes
// The space in the argument requires it to be quoted, the final backslash
// must not be allowed to turn the quote that is added into a literal
// instead of closing the quote.
new CMD(true,
ECHO_JAVA_ARGS,
Arrays.asList(SPACE_AND_BACKSLASH, "ARG_21"),
Arrays.asList(SPACE_AND_BACKSLASH, "ARG_21")),
new CMD(true,
ECHO_CMD_ARGS,
Arrays.asList(SPACE_AND_BACKSLASH, "ARG_22"),
Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + BACKSLASH + DOUBLE_QUOTE, "ARG_22")),
new CMD(true,
ECHO_VBS_ARGS,
Arrays.asList(SPACE_AND_BACKSLASH, "ARG_23"),
Arrays.asList(SPACE_AND_BACKSLASH + BACKSLASH, "ARG_23")),
// allowAmbiguousCommands = true, WITH application supplied double-quotes around the argument
// The argument has surrounding quotes so does not need further quoting.
// The backslash before the final quote is ignored and is interpreted differently for each command.
new CMD(true,
ECHO_JAVA_ARGS,
Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_31"),
Arrays.asList("SPACE AND BACKSLASH\" ARG_31")),
new CMD(true,
ECHO_CMD_ARGS,
Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_32"),
Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_32")),
new CMD(true,
ECHO_VBS_ARGS,
Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_33"),
Arrays.asList(SPACE_AND_BACKSLASH, "ARG_33"))
);
/**
* Common function to Invoke a process with the commands and check the result.
*
* @param cmd a CMD test case with arguments, allowAmbiguousCommands mode, and expected output
*/
private static void testCommand(CMD cmd) throws Exception {
System.setProperty(AMBIGUOUS_PROP_NAME, Boolean.toString(cmd.allowAmbiguous));
List<String> actual = null;
List<String> arguments = new ArrayList<>(cmd.command);
arguments.addAll(cmd.arguments);
try {
// Launch the process and wait for termination
ProcessBuilder pb = new ProcessBuilder(arguments);
Process process = pb.start();
try (InputStream is = process.getInputStream()) {
String str = readAllBytesAsString(is);
str = str.replace("\r", "");
actual = Arrays.asList(str.split("\n"));
} catch (IOException ioe) {
throw new RuntimeException(ioe.getMessage(), ioe);
}
int exitCode = process.waitFor();
if (exitCode != 0) {
actual = new ArrayList(actual);
actual.add("Exit code: " + exitCode);
}
} catch (IOException ioe) {
actual = Arrays.asList(ioe.getMessage().replace(arguments.get(0), "CMD"));
} catch (Exception ex) {
actual = Arrays.asList(ex.getMessage()); // Use exception message as output
}
if (!Objects.equals(actual, cmd.expected)) {
System.out.println("Invoking(" + cmd.allowAmbiguous + "): " + arguments);
if (actual.size() != cmd.expected.size()) {
System.out.println("Args Length: actual: " + actual.size() + " expected: " + cmd.expected.size());
}
System.out.println("Actual: " + actual);
System.out.println("Expected: " + cmd.expected);
System.out.println();
throw new RuntimeException("Unexpected output");
}
}
/**
* Private method to readAllBytes as a String.
* (InputStream.readAllBytes is not supported by the JDK until 9)
* @param is an InputStream
* @return a String with the contents
* @throws IOException if an error occurs
*/
private static String readAllBytesAsString(InputStream is) throws IOException {
final int BUF_SIZE = 8192;
byte[] bytes = new byte[BUF_SIZE];
int off = 0;
int len;
while ((len = is.read(bytes, off, bytes.length - off)) > 0) {
off += len;
if (off >= bytes.length) {
// no space in buffer, reallocate larger
bytes = Arrays.copyOf(bytes, bytes.length + BUF_SIZE);
}
}
return new String(bytes, 0, off, Charset.defaultCharset());
}
/**
* Initialize .cmd and .vbs scripts.
*
* @throws Error if an exception occurs
*/
private static void createFiles() throws IOException {
Files.write(Paths.get(ECHO_CMD_PATH), EchoArgumentsCmd.getBytes(StandardCharsets.UTF_8));
Files.write(Paths.get(ECHO_VBS_PATH), EchoArgumentsVbs.getBytes(StandardCharsets.UTF_8));
}
/**
* Self contained .cmd to echo each argument on a separate line.
*/
static final String EchoArgumentsCmd = "@echo off\n" +
"set p1=\n" +
"set p2=\n" +
"\n" +
"if not [%1]==[] set p1=%1\n" +
"if not [%2]==[] set p2=%2\n" +
"if not [%3]==[] set p3=%3\n" +
"if defined p1 echo %p1%\n" +
"if defined p2 echo %p2%\n" +
"if defined p3 echo %p3%\n" +
"exit /b 0\n";
/**
* Self contained .vbs to echo each argument on a separate line.
*/
static final String EchoArgumentsVbs = "Option Explicit\n" +
"Dim arg\n" +
"for each arg in WScript.Arguments\n" +
" WScript.StdOut.WriteLine(arg)\n" +
"Next\n";
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,103 @@
/*
* Copyright 2009 Google Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.util.*;
import java.io.*;
/**
* A manual test that demonstrates the ability to start a subprocess
* on Linux without getting ENOMEM. Run this test like:
*
* java -Xmx7000m BigFork
*
* providing a -Xmx flag suitable for your operating environment.
* Here's the bad old behavior:
*
* ==> java -Xmx7000m -esa -ea BigFork
* -------
* CommitLimit: 6214700 kB
* Committed_AS: 2484452 kB
* -------
* size=4.6GB
* -------
* CommitLimit: 6214700 kB
* Committed_AS: 7219680 kB
* -------
* Exception in thread "main" java.io.IOException: Cannot run program "/bin/true": java.io.IOException: error=12, Cannot allocate memory
* at java.lang.ProcessBuilder.start(ProcessBuilder.java:1018)
* at BigFork.main(BigFork.java:79)
* Caused by: java.io.IOException: java.io.IOException: error=12, Cannot allocate memory
* at java.lang.UNIXProcess.<init>(UNIXProcess.java:190)
* at java.lang.ProcessImpl.start(ProcessImpl.java:128)
* at java.lang.ProcessBuilder.start(ProcessBuilder.java:1010)
* ... 1 more
*/
public class BigFork {
static final Random rnd = new Random();
static void touchPages(byte[] chunk) {
final int pageSize = 4096;
for (int i = 0; i < chunk.length; i+= pageSize) {
chunk[i] = (byte) rnd.nextInt();
}
}
static void showCommittedMemory() throws IOException {
BufferedReader r =
new BufferedReader(
new InputStreamReader(
new FileInputStream("/proc/meminfo")));
System.out.println("-------");
String line;
while ((line = r.readLine()) != null) {
if (line.startsWith("Commit")) {
System.out.printf("%s%n", line);
}
}
System.out.println("-------");
}
public static void main(String[] args) throws Throwable {
showCommittedMemory();
final int chunkSize = 1024 * 1024 * 100;
List<byte[]> chunks = new ArrayList<byte[]>(100);
try {
for (;;) {
byte[] chunk = new byte[chunkSize];
touchPages(chunk);
chunks.add(chunk);
}
} catch (OutOfMemoryError e) {
chunks.set(0, null); // Free up one chunk
System.gc();
int size = chunks.size();
System.out.printf("size=%.2gGB%n", (double)size/10);
showCommittedMemory();
// Can we fork/exec in our current bloated state?
Process p = new ProcessBuilder("/bin/true").start();
p.waitFor();
}
}
}

View file

@ -0,0 +1,188 @@
/*
* Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test id=Default
* @bug 8024521 8315721
* @summary Closing ProcessPipeInputStream at the time the process exits is racy
* and leads to data corruption. Run this test manually (as
* an ordinary java program) with -Xmx8M to repro bug 8024521.
* @requires vm.gc != "Z"
* @comment Don't allow -Xcomp, it disturbs the timing
* @requires (vm.compMode != "Xcomp")
* @run main/othervm -Xmx8M -Dtest.duration=2 CloseRace
*/
/**
* @test id=Z
* @comment Turn up heap size to lower amount of GCs
* @requires vm.gc.Z
* @comment Don't allow -Xcomp, it disturbs the timing
* @requires (vm.compMode != "Xcomp")
* @run main/othervm -XX:+UseZGC -Xmx32M -Dtest.duration=2 CloseRace
*/
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
public class CloseRace {
private static final String BIG_FILE = "bigfile";
private static final int[] procFDs = new int[6];
/** default value sufficient to repro bug 8024521. */
private static final int testDurationSeconds
= Integer.getInteger("test.duration", 600);
private static final CountDownLatch threadsStarted
= new CountDownLatch(2);
static boolean fdInUse(int i) {
return new File("/proc/self/fd/" + i).exists();
}
static boolean[] procFDsInUse() {
boolean[] inUse = new boolean[procFDs.length];
for (int i = 0; i < procFDs.length; i++)
inUse[i] = fdInUse(procFDs[i]);
return inUse;
}
static int count(boolean[] bits) {
int count = 0;
for (int i = 0; i < bits.length; i++)
count += bits[i] ? 1 : 0;
return count;
}
static void dumpAllStacks() {
System.err.println("Start of dump");
final Map<Thread, StackTraceElement[]> allStackTraces
= Thread.getAllStackTraces();
for (Thread thread : allStackTraces.keySet()) {
System.err.println("Thread " + thread.getName());
for (StackTraceElement element : allStackTraces.get(thread))
System.err.println("\t" + element);
}
System.err.println("End of dump");
}
public static void main(String args[]) throws Exception {
if (!(new File("/proc/self/fd").isDirectory()))
return;
// Catch Errors from process reaper
Thread.setDefaultUncaughtExceptionHandler
((t, e) -> { e.printStackTrace(); System.exit(1); });
try (RandomAccessFile f = new RandomAccessFile(BIG_FILE, "rw")) {
f.setLength(Runtime.getRuntime().maxMemory()); // provoke OOME
}
for (int i = 0, j = 0; j < procFDs.length; i++)
if (!fdInUse(i))
procFDs[j++] = i;
Thread[] threads = {
new Thread(new OpenLoop()),
new Thread(new ExecLoop()),
};
for (Thread thread : threads)
thread.start();
threadsStarted.await();
Thread.sleep(testDurationSeconds * 1000);
for (Thread thread : threads)
thread.interrupt();
for (Thread thread : threads) {
thread.join(10_000);
if (thread.isAlive()) {
dumpAllStacks();
throw new Error("At least one child thread ("
+ thread.getName()
+ ") failed to finish gracefully");
}
}
}
static class OpenLoop implements Runnable {
public void run() {
threadsStarted.countDown();
while (!Thread.interrupted()) {
try {
// wait for ExecLoop to finish creating process
do {
if (Thread.interrupted())
return;
} while (count(procFDsInUse()) != 3);
List<InputStream> iss = new ArrayList<>(4);
// eat up three "holes" (closed ends of pipe fd pairs)
for (int i = 0; i < 3; i++)
iss.add(new FileInputStream(BIG_FILE));
do {
if (Thread.interrupted())
return;
} while (count(procFDsInUse()) == procFDs.length);
// hopefully this will racily occupy empty fd slot
iss.add(new FileInputStream(BIG_FILE));
Thread.sleep(1); // Widen race window
for (InputStream is : iss)
is.close();
} catch (InterruptedException e) {
break;
} catch (Exception e) {
throw new Error(e);
}
}
}
}
static class ExecLoop implements Runnable {
public void run() {
threadsStarted.countDown();
ProcessBuilder builder = new ProcessBuilder("/bin/true");
while (!Thread.interrupted()) {
try {
// wait for OpenLoop to finish
do {
if (Thread.interrupted())
return;
} while (count(procFDsInUse()) > 0);
Process process = builder.start();
InputStream is = process.getInputStream();
process.waitFor();
is.close();
} catch (InterruptedException e) {
break;
} catch (Exception e) {
throw new Error(e);
}
}
}
}
}

View file

@ -0,0 +1,129 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2026, IBM Corp.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test id=POSIX_SPAWN
* @bug 8377907
* @summary Test that demonstrates the hanging-parent-on-native-concurrent-forks problem
* @requires os.family != "windows"
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/manual -Djdk.lang.Process.launchMechanism=POSIX_SPAWN ConcNativeForkTest
*/
/*
* @test id=FORK
* @bug 8377907
* @summary Test that demonstrates the hanging-parent-on-native-concurrent-forks problem
* @requires os.family != "windows"
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/manual -Djdk.lang.Process.launchMechanism=FORK ConcNativeForkTest
*/
/*
* @test id=VFORK
* @bug 8377907
* @summary Test that demonstrates the hanging-parent-on-native-concurrent-forks problem
* @requires os.family == "linux"
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/manual -Djdk.lang.Process.launchMechanism=VFORK ConcNativeForkTest
*/
public class ConcNativeForkTest {
// How this works:
// - We start a child process via ProcessBuilder. Does not matter what, we just call "/bin/true".
// - Concurrently, we continuously (up to a limit) fork natively; these forks will all exec "sleep 30".
// - If the natively forked child process forks off at the right (wrong) moment, it will catch the open pipe from
// the "/bin/true" child process, and forcing the parent process (this test) to wait in ProcessBuilder.start()
// (inside forkAndExec()) until the natively forked child releases the pipe file descriptors it inherited.
// Notes:
//
// Obviously, this is racy and depends on scheduler timings of the underlying OS. The test succeeding is
// no proof the bug does not exist (see PipesCloseOnExecTest as a complimentary test that is more reliable, but
// only works on Linux).
// That said, in tests it reliably reproduces the bug on Linux x64 and MacOS Arm.
//
// This test is not well suited for automatic test execution, since the test essentially
// fork-bombs itself, and that may run into issues in containerized CI/CD environments.
native static boolean prepareNativeForkerThread(int numForks);
native static void releaseNativeForkerThread();
native static void stopNativeForkerThread();
private static final int numIterations = 20;
public static void main(String[] args) throws Exception {
System.out.println("jdk.lang.Process.launchMechanism=" +
System.getProperty("jdk.lang.Process.launchMechanism"));
System.loadLibrary("ConcNativeFork");
// A very simple program returning immediately (/bin/true)
ProcessBuilder pb = new ProcessBuilder("true").inheritIO();
final int numJavaProcesses = 10;
final int numNativeProcesses = 250;
Process[] processes = new Process[numJavaProcesses];
for (int iteration = 0; iteration < numIterations; iteration ++) {
if (!prepareNativeForkerThread(numNativeProcesses)) {
throw new RuntimeException("Failed to start native forker thread (see stdout)");
}
long[] durations = new long[numJavaProcesses];
releaseNativeForkerThread();
for (int np = 0; np < numJavaProcesses; np ++) {
long t1 = System.currentTimeMillis();
try (Process p = pb.start()) {
durations[np] = System.currentTimeMillis() - t1;
processes[np] = p;
}
}
stopNativeForkerThread();
long longestDuration = 0;
for (int np = 0; np < numJavaProcesses; np ++) {
processes[np].waitFor();
System.out.printf("Duration: %dms%n", durations[np]);
longestDuration = Math.max(durations[np], longestDuration);
}
System.out.printf("Longest startup time: %dms%n", longestDuration);
if (longestDuration >= 30000) {
throw new RuntimeException("Looks like we blocked on native fork");
}
}
}
}

View file

@ -0,0 +1,147 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2026, IBM Corp.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <jni.h>
#include "testlib_thread_barriers.h"
static void trc(const char* fmt, ...) {
char buf [1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
printf("(pid: %d): %s\n", (int)getpid(), buf);
fflush(stdout);
}
static pthread_t tid_forker;
static pthread_barrier_t start_barrier;
static atomic_bool stop_now = false;
static void* forkerLoop(void* info) {
const int numForks = (int)(intptr_t)info;
pid_t* pids = calloc(numForks, sizeof(pid_t));
trc("Forker: Waiting for Go.");
pthread_barrier_wait(&start_barrier);
for (int i = 0; i < numForks; i++) {
const pid_t pid = fork();
if (pid == 0) {
/* Exec sleep. Properly opened file descriptors in parents (tagged CLOEXEC) should be released now.
* Note that we use bash to not have to deal with path resolution. For our case, it does not matter if
* sleep is a builtin or not. */
char* env[] = { "PATH=/usr/bin:/bin", NULL };
char* argv[] = { "sh", "-c", "sleep 30", NULL };
execve("/bin/sh", argv, env);
trc("Native child: sleep exec failed? %d", errno);
/* The simplest way to handle this is to just wait here; this *will* cause the test to fail. */
sleep(120);
trc("Native child: exiting");
exit(0);
} else {
pids[i] = pid;
sched_yield();
}
}
trc("Forker: All native child processes started.");
/* Wait for test to signal end */
while (!atomic_load(&stop_now)) {
sleep(1);
}
trc("Forker: Cleaning up.");
/* Reap children */
for (int i = 0; i < numForks; i ++) {
if (pids[i] != 0) {
kill(pids[i], SIGKILL); /* if still running */
waitpid(pids[i], NULL, 0);
}
}
trc("Forker: Done.");
return NULL;
}
JNIEXPORT jboolean JNICALL
Java_ConcNativeForkTest_prepareNativeForkerThread(JNIEnv* env, jclass cls, jint numForks)
{
pthread_attr_t attr;
int rc = 0;
const int cap = 1000;
const int numForksCapped = numForks > cap ? cap : numForks;
if (numForks > numForksCapped) {
trc("Main: Capping max. number of forks at %d", numForksCapped); /* don't forkbomb me */
}
if (pthread_barrier_init(&start_barrier, NULL, 2) != 0) {
trc("Main: pthread_barrier_init failed (%d)", errno);
return false;
}
pthread_attr_init(&attr);
if (pthread_create(&tid_forker, &attr, forkerLoop, (void*)(intptr_t)numForksCapped) != 0) {
trc("Main: pthread_create failed (%d)", errno);
return JNI_FALSE;
}
trc("Main: Prepared native forker thread");
return JNI_TRUE;
}
JNIEXPORT void JNICALL
Java_ConcNativeForkTest_releaseNativeForkerThread(JNIEnv* env, jclass cls)
{
pthread_barrier_wait(&start_barrier);
trc("Main: signaled GO");
}
JNIEXPORT void JNICALL
Java_ConcNativeForkTest_stopNativeForkerThread(JNIEnv* env, jclass cls)
{
atomic_store(&stop_now, true);
pthread_join(tid_forker, NULL);
pthread_barrier_destroy(&start_barrier);
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 4244896
* @summary Test for the various platform specific implementations of
* destroyForcibly.
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
abstract class ProcessTest implements Runnable {
ProcessBuilder bldr;
Process p;
public void run() {
try {
String line;
BufferedReader is =
new BufferedReader(new InputStreamReader(p.getInputStream()));
while ((line = is.readLine()) != null)
System.err.println("ProcessTrap: " + line);
} catch(IOException e) {
if (!e.getMessage().matches("[Ss]tream [Cc]losed")) {
throw new RuntimeException(e);
}
}
}
public void runTest() throws Exception {
// The destroy() method is not tested because
// the process streams are closed by the destroy() call.
// After a destroy() call, the process terminates with a
// SIGPIPE even if it was trapping SIGTERM.
// So skip the trap test and go straight to destroyForcibly().
p.destroyForcibly();
p.waitFor();
if (p.isAlive())
throw new RuntimeException("Problem terminating the process.");
}
}
class UnixTest extends ProcessTest {
public UnixTest(File script) throws IOException {
script.deleteOnExit();
createScript(script);
bldr = new ProcessBuilder(script.getCanonicalPath());
bldr.redirectErrorStream(true);
bldr.directory(new File("."));
p = bldr.start();
}
void createScript(File processTrapScript) throws IOException {
processTrapScript.deleteOnExit();
try (FileWriter fstream = new FileWriter(processTrapScript);
BufferedWriter out = new BufferedWriter(fstream)) {
out.write("#!/bin/bash\n" +
"echo \\\"ProcessTrap.sh started\\\"\n" +
"while :\n" +
"do\n" +
" sleep 1;\n" +
"done\n");
}
processTrapScript.setExecutable(true, true);
}
}
class WindowsTest extends ProcessTest {
public WindowsTest() throws IOException {
bldr = new ProcessBuilder("ftp");
bldr.redirectErrorStream(true);
bldr.directory(new File("."));
p = bldr.start();
}
}
public class DestroyTest {
public static ProcessTest getTest() throws Exception {
String osName = System.getProperty("os.name");
if (osName.startsWith("Windows")) {
return new WindowsTest();
} else {
File userDir = new File(System.getProperty("user.dir", "."));
File tempFile = File.createTempFile("ProcessTrap-", ".sh", userDir);
if (osName.startsWith("Linux")
|| osName.startsWith("Mac OS")
|| osName.equals("AIX")) {
return new UnixTest(tempFile);
}
}
return null;
}
public static void main(String args[]) throws Exception {
ProcessTest test = getTest();
if (test == null) {
throw new RuntimeException("Unrecognised OS");
} else {
new Thread(test).start();
Thread.sleep(1000);
test.runTest();
}
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test id=posix_spawn
* @summary Check that we don't leak FDs to child processes
* @requires os.family != "windows"
* @library /test/lib
* @run main/othervm/native -Djdk.lang.Process.launchMechanism=posix_spawn -agentlib:FDLeaker FDLeakTest
*/
/**
* @test id=fork
* @summary Check that we don't leak FDs to child processes
* @requires os.family != "windows"
* @library /test/lib
* @run main/othervm/native -Djdk.lang.Process.launchMechanism=fork -agentlib:FDLeaker FDLeakTest
*/
/**
* @test id=vfork
* @summary Check that we don't leak FDs to child processes
* @requires os.family == "linux"
* @library /test/lib
* @run main/othervm/native -Djdk.lang.Process.launchMechanism=vfork -agentlib:FDLeaker FDLeakTest
*/
import jdk.test.lib.process.ProcessTools;
public class FDLeakTest {
// This test has two native parts:
// - a library invoked with -agentlib that ensures that, in the parent JVM, we open
// a native fd without setting FD_CLOEXEC (libFDLeaker.c). This is necessary because
// there is no way to do this from Java: if Java functions correctly, all files the
// user could open via its APIs should be marked with FD_CLOEXEC.
// - a small native executable that tests - without using /proc - whether any file
// descriptors other than stdin/out/err are open.
//
// What should happen: In the child process, between the initial fork and the exec of
// the target binary, we should close all filedescriptors that are not stdin/out/err.
// If that works, the child process should not see any other file descriptors save
// those three.
public static void main(String[] args) throws Exception {
ProcessBuilder pb = ProcessTools.createNativeTestProcessBuilder("FDLeakTester");
pb.inheritIO();
Process p = pb.start();
p.waitFor();
if (p.exitValue() != 0) {
throw new RuntimeException("Failed");
}
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/* Check if any fd past stderr is valid; if true, print warning on stderr and return -1
*
* Note: check without accessing /proc since:
* - non-portable
* - may cause creation of temporary file descriptors
*/
int main(int argc, char** argv) {
int errors = 0;
int rc = 0;
char buf[128];
int max_fd = (int)sysconf(_SC_OPEN_MAX);
if (max_fd == -1) {
snprintf(buf, sizeof(buf), "*** sysconf(_SC_OPEN_MAX) failed? (%d) ***\n", errno);
rc = write(2, buf, strlen(buf));
max_fd = 10000;
}
// We start after stderr fd
for (int fd = 3; fd < max_fd; fd++) {
if (fcntl(fd, F_GETFD, 0) >= 0) {
// Error: found valid file descriptor
errors++;
snprintf(buf, sizeof(buf), "*** Parent leaked file descriptor %d ***\n", fd);
rc = write(2, buf, strlen(buf));
}
}
return errors > 0 ? -1 : 0;
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include "jvmti.h"
static jint limit_num_fds();
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
// Lower the number of possible open files to make the test go faster
jint ret = limit_num_fds();
if (ret != 0) {
fprintf(stderr, "Failed to limit number of fds: %s", strerror(errno));
return ret;
}
const char* filename = "./testfile_FDLeaker.txt";
FILE* f = fopen(filename, "w");
if (f == NULL) {
fprintf(stderr, "Failed to open file: %s", strerror(errno));
return JNI_ERR;
}
printf("Opened and leaked %s (%d)", filename, fileno(f));
return JNI_OK;
}
static jint limit_num_fds() {
struct rlimit rl;
// Fetch the current limit
int ret = getrlimit(RLIMIT_NOFILE, &rl);
if (ret != 0) {
return JNI_ERR;
}
// Use a lower value unless it is already low
rlim_t limit = 100;
if (limit < rl.rlim_cur) {
rl.rlim_cur = limit;
}
// Lower the value
int ret2 = setrlimit(RLIMIT_NOFILE, &rl);
if (ret2 != 0) {
return JNI_ERR;
}
return JNI_OK;
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* A test for
* 6469606: (process) Process.destroy() can kill wrong process (Unix)
* that is not suitable for use as a regression test, because it must
* create 32768 processes, and depends on perl.
*/
import java.io.*;
public class FeelingLucky {
public static void main(String[] args) throws Throwable {
final Runtime rt = Runtime.getRuntime();
final String[] pidPrinter = {"/bin/sh", "-c", "echo $$"};
final Process minedProcess = rt.exec(pidPrinter);
int minedPid = 0;
final InputStream is = minedProcess.getInputStream();
int c;
while ((c = is.read()) >= '0' && c <= '9')
minedPid = 10 * minedPid + (c - '0');
System.out.printf("minedPid=%d%n", minedPid);
minedProcess.waitFor();
final String[] magnum = {
"perl", "-e",
"my $punk = getppid();" +
"open TTY, '> /dev/tty';" +
"print TTY \"punk=$punk\\n\";" +
"for (my $i = 0; $i < 32768; $i++) {" +
" my $pid = fork();" +
" if ($pid == 0) {" +
" if ($$ == " + minedPid + ") {" +
" print TTY \"MATCH $$ $punk\\n\";" +
" $SIG{TERM} = sub {" +
" print TTY \"Got TERM $$ $punk\\n\";" +
" kill 9, $punk;" +
" exit; };" +
" $| = 1; print 'Go ahead. Make my day.';" +
" sleep 100;" +
" }" +
" exit;" +
" } else { " +
" waitpid($pid,0);" +
" break if $pid == " + minedPid + ";" +
" }" +
"}"
};
Process p = rt.exec(magnum);
if (p.getInputStream().read() != -1) {
System.out.println("got a char");
minedProcess.destroy();
Thread.sleep(1000);
}
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary child process should not hang even if parent process has closed System.out/err
* @bug 8366736
* @run main/othervm InheritIOClosed close dontclose
* @run main/othervm InheritIOClosed dontclose close
* @run main/othervm InheritIOClosed close close
*/
import java.nio.file.Path;
public class InheritIOClosed {
private final static Path JAVA_EXE =
Path.of(System.getProperty("java.home"), "bin", "java");
private static final String s = "1234567890".repeat(10);
public static void main(String[] args) throws Exception {
if (args.length == 2) {
// Main test process
if (args[0].equals("close")) {
System.out.close();
}
if (args[1].equals("close")) {
System.err.close();
}
ProcessBuilder pb = new ProcessBuilder().inheritIO()
.command(JAVA_EXE.toString(),
"-cp",
System.getProperty("java.class.path"),
InheritIOClosed.class.getName());
Process process = pb.start();
process.waitFor();
System.out.println("Done");
} else {
// Child process -- print to System.out/err. Without the fix in
// JDK-8366736, this process will hang on Windows.
for (int i = 0; i < 100; i++) {
System.out.println(s);
}
for (int i = 0; i < 100; i++) {
System.err.println(s);
}
}
}
}

View file

@ -0,0 +1,166 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 7147084
* @run main/othervm InheritIOEHandle
* @summary inherit IOE handles and MS CreateProcess limitations (kb315939)
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
public class InheritIOEHandle {
private static enum APP {
A, B, C;
}
private static File stopC = new File("StopC.txt");
private static String SIGNAL = "After call child process";
private static String JAVA_EXE = System.getProperty("java.home")
+ File.separator + "bin"
+ File.separator + "java";
private static String[] getCommandArray(String processName) {
String[] cmdArray = {
JAVA_EXE,
"-cp",
System.getProperty("java.class.path"),
InheritIOEHandle.class.getName(),
processName
};
return cmdArray;
}
public static void main(String[] args) throws Exception {
if (!System.getProperty("os.name").startsWith("Windows")) {
return;
}
APP app = (args.length > 0) ? APP.valueOf(args[0]) : APP.A;
switch (app) {
case A:
performA();
break;
case B:
performB();
break;
case C:
performC();
break;
}
}
private static void performA() {
try {
stopC.delete();
ProcessBuilder builder = new ProcessBuilder(
getCommandArray(APP.B.name()));
builder.redirectErrorStream(true);
Process process = builder.start();
process.getOutputStream().close();
process.getErrorStream().close();
boolean isSignalReceived = false;
try (BufferedReader in = new BufferedReader(new InputStreamReader(
process.getInputStream(), "utf-8")))
{
String result;
while ((result = in.readLine()) != null) {
if (SIGNAL.equals(result)) {
isSignalReceived = true;
break;
} else {
throw new RuntimeException("Catastrophe in process B! Bad output.");
}
}
}
if (!isSignalReceived) {
throw new RuntimeException("Signal from B was not received");
}
// If JDK-7147084 is not fixed that point is unreachable.
System.out.println("Received signal from B, creating file StopC");
// write signal file
boolean isFileStopC = stopC.createNewFile();
if (!isFileStopC) {
throw new RuntimeException("Signal file StopC.txt was not created. TEST or INFRA bug");
}
process.waitFor();
System.err.println("Read stream finished.");
} catch (IOException ex) {
throw new RuntimeException("Catastrophe in process A!", ex);
} catch (InterruptedException ex) {
throw new RuntimeException("A was interrupted while waiting for B", ex);
}
}
private static void performB() {
try {
ProcessBuilder builder = new ProcessBuilder(
getCommandArray(APP.C.name()));
Process process = builder.start();
process.getInputStream().close();
process.getOutputStream().close();
process.getErrorStream().close();
System.out.println(SIGNAL);
process.waitFor();
// JDK-7147084 subject:
// Process C inherits the [System.out] handle and
// handle close in B does not finalize the streaming for A.
// (handle reference count > 1).
} catch (IOException ex) {
throw new RuntimeException("Catastrophe in process B!", ex);
} catch (InterruptedException ex) {
throw new RuntimeException("B was interrupted while waiting for C", ex);
}
}
private static void performC() {
// If JDK-7147084 is not fixed the loop is 5min long.
for (int i = 0; i < 5 * 60; ++i) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
// that is ok. Longer sleep - better effect.
}
// check for success
if (stopC.exists()) {
break;
}
}
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Unit test for java.lang.ProcessBuilder inheritance of standard output and standard error streams
* @bug 8023130 8166026
* @requires vm.flagless
* @library /test/lib
* @build jdk.test.lib.process.*
* @run junit InheritIOTest
*/
import java.util.List;
import static java.lang.ProcessBuilder.Redirect.INHERIT;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class InheritIOTest {
private static final String EXIT_VALUE_TEMPLATE = "exit value: %d";
private static final String EXPECTED_RESULT_STDOUT = "message";
private static final String EXPECTED_RESULT_STDERR = EXIT_VALUE_TEMPLATE.formatted(0);
public static List<Arguments> testCases() {
return List.of(
Arguments.of(List.of("InheritIOTest$TestInheritIO", "printf", EXPECTED_RESULT_STDOUT)),
Arguments.of(List.of("InheritIOTest$TestRedirectInherit", "printf", EXPECTED_RESULT_STDOUT))
);
}
@ParameterizedTest
@MethodSource("testCases")
public void testInheritWithoutRedirect(List<String> arguments) throws Throwable {
ProcessBuilder processBuilder = ProcessTools.createLimitedTestJavaProcessBuilder(arguments);
OutputAnalyzer outputAnalyzer = ProcessTools.executeCommand(processBuilder);
outputAnalyzer.shouldHaveExitValue(0);
assertEquals(EXPECTED_RESULT_STDOUT, outputAnalyzer.getStdout());
assertEquals(EXPECTED_RESULT_STDERR, outputAnalyzer.getStderr());
}
public static class TestInheritIO {
public static void main(String[] args) throws Throwable {
int err = new ProcessBuilder(args).inheritIO().start().waitFor();
System.err.printf(EXIT_VALUE_TEMPLATE, err);
System.exit(err);
}
}
public static class TestRedirectInherit {
public static void main(String[] args) throws Throwable {
int err = new ProcessBuilder(args)
.redirectInput(INHERIT)
.redirectOutput(INHERIT)
.redirectError(INHERIT)
.start().waitFor();
System.err.printf(EXIT_VALUE_TEMPLATE, err);
System.exit(err);
}
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test id=FORK
* @bug 8379967
* @summary Check that passing an invalid work dir yields a corresponding IOE text.
* @requires (os.family != "windows")
* @requires vm.flagless
* @library /test/lib
* @run main/othervm -Xmx64m -Djdk.lang.Process.launchMechanism=FORK InvalidWorkDir
*/
/**
* @test id=POSIX_SPAWN
* @bug 8379967
* @summary Check that passing an invalid work dir yields a corresponding IOE text.
* @requires (os.family != "windows")
* @requires vm.flagless
* @library /test/lib
* @run main/othervm -Xmx64m -Djdk.lang.Process.launchMechanism=FORK InvalidWorkDir
*/
import jdk.test.lib.process.OutputAnalyzer;
import java.io.File;
import java.io.IOException;
public class InvalidWorkDir {
public static void main(String[] args) {
ProcessBuilder bld = new ProcessBuilder("ls").directory(new File("./doesnotexist"));
try(Process p = bld.start()) {
throw new RuntimeException("IOE expected");
} catch (IOException e) {
if (!e.getMessage().matches(".*Failed to access working directory.*No such file or directory.*")) {
throw new RuntimeException(String.format("got IOE but with different text (%s)", e.getMessage()));
}
}
}
}

View file

@ -0,0 +1,245 @@
/*
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
/*
* @test
* @bug 8307990
* @requires (os.family == "linux") | (os.family == "aix") | (os.family == "mac")
* @requires vm.debug
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/timeout=300 JspawnhelperProtocol
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
public class JspawnhelperProtocol {
// Timout in seconds
private static final int TIMEOUT = 60;
// Base error code to communicate various error states from the parent process to the top-level test
private static final int ERROR = 10;
private static final String[] CMD = { "pwd" };
private static final String ENV_KEY = "JTREG_JSPAWNHELPER_PROTOCOL_TEST";
private static final String SPAWNHELPER_FAILURE_MSG = "Possible reasons:";
private static void parentCode(String arg) throws IOException, InterruptedException {
System.out.println("Recursively executing 'JspawnhelperProtocol " + arg + "'");
Process p = null;
try {
// Route any stdout from the child process - be it jspawnhelper error messages or the output of "/bin/pwd" -
// through to the parent process.
p = new ProcessBuilder(CMD).inheritIO().start();
} catch (Exception e) {
// Check that exception contains rich message on failure.
e.printStackTrace(System.out);
if (e instanceof IOException && e.getMessage().contains(SPAWNHELPER_FAILURE_MSG)) {
System.exit(ERROR);
} else {
System.exit(ERROR + 3);
}
}
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
System.out.println("Child process timed out");
System.exit(ERROR + 1);
}
if (p.exitValue() == 0) {
System.out.println(" Successfully executed '" + CMD[0] + "'");
System.exit(0);
} else {
System.out.println(" Failed to executed '" + CMD[0] + "' (exitValue=" + p.exitValue() + ")");
System.exit(ERROR + 3);
}
}
private static void normalExec() throws Exception {
ProcessBuilder pb;
pb = ProcessTools.createLimitedTestJavaProcessBuilder("-Djdk.lang.Process.launchMechanism=posix_spawn",
"JspawnhelperProtocol",
"normalExec");
Process p = pb.start();
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
throw new Exception("Parent process timed out");
}
if (p.exitValue() != 0) {
throw new Exception("Parent process exited with " + p.exitValue());
}
OutputAnalyzer output = new OutputAnalyzer(p);
output.shouldContain("Recursively executing 'JspawnhelperProtocol normalExec'");
String realPwd = Path.of("").toAbsolutePath().toString();
output.shouldContain(realPwd);
}
private static void simulateCrashInChild(int stage) throws Exception {
ProcessBuilder pb;
pb = ProcessTools.createLimitedTestJavaProcessBuilder("-Djdk.lang.Process.launchMechanism=posix_spawn",
"JspawnhelperProtocol",
"simulateCrashInChild" + stage);
pb.environment().put(ENV_KEY, Integer.toString(stage));
Process p = pb.start();
boolean foundCrashInfo = false;
try (BufferedReader br = p.inputReader()) {
String line = br.readLine();
while (line != null) {
System.out.println(line);
if (line.equals("posix_spawn:0")) {
foundCrashInfo = true;
}
line = br.readLine();
}
}
if (!foundCrashInfo) {
throw new Exception("Wrong output from child process");
}
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
throw new Exception("Parent process timed out");
}
int ret = p.exitValue();
if (ret == 0) {
throw new Exception("Expected error during child execution");
}
System.out.println("Parent exit code: " + ret);
}
private static void simulateCrashInParent(int stage) throws Exception {
ProcessBuilder pb;
pb = ProcessTools.createLimitedTestJavaProcessBuilder("-Djdk.lang.Process.launchMechanism=posix_spawn",
"JspawnhelperProtocol",
"simulateCrashInParent" + stage);
pb.environment().put(ENV_KEY, Integer.toString(stage));
Process p = pb.start();
String line = null;
try (BufferedReader br = p.inputReader()) {
line = br.readLine();
while (line != null && !line.startsWith("posix_spawn:")) {
System.out.println("parent stdout:" + line);
line = br.readLine();
}
}
if (line == null) {
throw new Exception("Wrong output from parent process");
}
System.out.println(line);
long childPid = Integer.parseInt(line.substring(line.indexOf(':') + 1));
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
throw new Exception("Parent process timed out");
}
Optional<ProcessHandle> oph = ProcessHandle.of(childPid);
if (!oph.isEmpty()) {
ProcessHandle ph = oph.get();
try {
// Give jspawnhelper a chance to exit gracefully
ph.onExit().get(TIMEOUT, TimeUnit.SECONDS);
} catch (TimeoutException te) {
Optional<String> cmd = ph.info().command();
if (cmd.isPresent() && cmd.get().endsWith("jspawnhelper")) {
throw new Exception("jspawnhelper still alive after parent Java process terminated");
}
}
}
int ret = p.exitValue();
if (ret != stage) {
throw new Exception("Expected exit code " + stage + " but got " + ret);
}
System.out.println("Parent exit code: " + ret);
}
private static void simulateTruncatedWriteInParent(int stage) throws Exception {
ProcessBuilder pb;
pb = ProcessTools.createLimitedTestJavaProcessBuilder("-Djdk.lang.Process.launchMechanism=posix_spawn",
"JspawnhelperProtocol",
"simulateTruncatedWriteInParent" + stage);
pb.environment().put(ENV_KEY, Integer.toString(stage));
Process p = pb.start();
BufferedReader br = p.inputReader();
String line = br.readLine();
while (line != null && !line.startsWith("posix_spawn:")) {
System.out.println(line);
line = br.readLine();
}
if (line == null) {
throw new Exception("Wrong output from parent process");
}
System.out.println(line);
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
throw new Exception("Parent process timed out");
}
line = br.readLine();
while (line != null) {
System.out.println(line);
line = br.readLine();
}
int ret = p.exitValue();
if (ret != ERROR) {
throw new Exception("Expected exit code " + ERROR + " but got " + ret);
}
System.out.println("Parent exit code: " + ret);
}
public static void main(String[] args) throws Exception {
// This test works as follows:
// - jtreg executes the test class `JspawnhelperProtocol` without arguments.
// This is the initial "grandparent" process.
// - For each sub-test (i.e. `normalExec()`, `simulateCrashInParent()` and
// `simulateCrashInChild()`), a new sub-process (called the "parent") will be
// forked which executes `JspawnhelperProtocol` recursively with a corresponding
// command line argument.
// - The forked `JspawnhelperProtocol` process (i.e. the "parent") runs
// `JspawnhelperProtocol::parentCode()` which forks off yet another sub-process
// (called the "child").
// - The sub-tests in the "grandparent" check that various abnormal program
// terminations in the "parent" or the "child" process are handled gracefully and
// don't lead to deadlocks or zombie processes.
if (args.length > 0) {
// Entry point for recursive execution in the "parent" process
parentCode(args[0]);
} else {
// Main test entry for execution from jtreg
normalExec();
simulateCrashInParent(1);
simulateCrashInParent(2);
simulateCrashInParent(3);
simulateCrashInChild(4);
simulateCrashInChild(5);
simulateCrashInChild(6);
simulateTruncatedWriteInParent(99);
}
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test id=badargs
* @bug 8325567 8325621 8379967
* @requires (os.family == "linux") | (os.family == "aix") | (os.family == "mac")
* @library /test/lib
* @run driver JspawnhelperWarnings badargs
*/
/*
* @test id=badversion
* @bug 8325567 8325621 8379967
* @requires (os.family == "linux") | (os.family == "aix") | (os.family == "mac")
* @library /test/lib
* @run driver JspawnhelperWarnings badversion
*/
import java.nio.file.Paths;
import java.util.Arrays;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
public class JspawnhelperWarnings {
// See childproc_errorcodes.h
static final int ESTEP_JSPAWN_ARG_ERROR = 1;
static final int ESTEP_JSPAWN_VERSION_ERROR = 2;
// See exitCodeFromErrorCode() in childproc_errorcodes.c
static final int EXITCODE_OFFSET = 0x10;
private static void tryWithNArgs(int nArgs) throws Exception {
System.out.println("Running jspawnhelper with " + nArgs + " args");
String[] args = new String[nArgs + 1];
Arrays.fill(args, "1");
args[0] = Paths.get(System.getProperty("java.home"), "lib", "jspawnhelper").toString();
Process p = ProcessTools.startProcess("jspawnhelper", new ProcessBuilder(args));
OutputAnalyzer oa = new OutputAnalyzer(p);
oa.shouldHaveExitValue(EXITCODE_OFFSET + ESTEP_JSPAWN_ARG_ERROR);
oa.shouldContain("jspawnhelper fail: (1-0-0)");
oa.shouldContain("This command is not for general use");
if (nArgs != 1) {
oa.shouldContain("Incorrect number of arguments");
} else {
oa.shouldContain("Incorrect Java version");
}
}
private static void testVersion() throws Exception {
String[] args = new String[2];
args[0] = Paths.get(System.getProperty("java.home"), "lib", "jspawnhelper").toString();
args[1] = "wrongVersion";
Process p = ProcessTools.startProcess("jspawnhelper", new ProcessBuilder(args));
OutputAnalyzer oa = new OutputAnalyzer(p);
oa.shouldHaveExitValue(EXITCODE_OFFSET + ESTEP_JSPAWN_VERSION_ERROR);
oa.shouldContain("jspawnhelper fail: (2-0-0)");
oa.shouldContain("This command is not for general use");
oa.shouldContain("Incorrect Java version: wrongVersion");
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
throw new RuntimeException("test argument error");
}
switch (args[0]) {
case "badargs" -> {
for (int nArgs = 0; nArgs < 10; nArgs++) {
if (nArgs != 1) {
tryWithNArgs(nArgs);
}
}
}
case "badversion" -> {
testVersion();
}
default -> throw new RuntimeException("test argument error");
}
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Accessor for Native calls to get information about File Descriptors.
* If it's a pipe and its Inode number.
*/
public class LinuxFDInfo {
static {
System.loadLibrary("LinuxFDInfo");
}
// Maximum file descriptor to probe for being a pipe,
private final static int MAX_FD = 100;
// A simple main to print the pipes in this process and their Inode value
public static void main() {
for (int fd = 0; fd < MAX_FD; fd++) {
long inode = getPipeInodeNum(fd);
if (inode != 0) {
System.out.printf("fd: %d, inode: 0x%08x\n", fd, inode);
}
}
}
// Parse the output from main into a long array of the fd, and Inode.
public static FdAndInode parseFdAndInode(String s) {
String[] args = s.split(",");
return new FdAndInode(Integer.parseUnsignedInt(args[0].split(":")[1].trim()),
Long.parseUnsignedLong(args[1].split(":")[1].trim().substring(2), 16));
}
/**
* Return the inode number for the FD, if it is a pipe.
* @param fd file descriptor
* @return the Inode number.
*/
public static native long getPipeInodeNum(int fd);
public record FdAndInode(int fd, long inode) {}
}

View file

@ -0,0 +1,182 @@
/*
* Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2026, IBM Corp.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.*;
/*
* @test id=FORK
* @summary Check that we don't accumulate leaked FDs in the parent process
* @requires os.family == "linux"
* @library /test/lib
* @run main/othervm -Djdk.lang.Process.launchMechanism=fork NonPipelineLeaksFD
*/
/*
* @test id=VFORK
* @summary Check that we don't accumulate leaked FDs in the parent process
* @requires os.family == "linux"
* @library /test/lib
* @run main/othervm -Djdk.lang.Process.launchMechanism=vfork NonPipelineLeaksFD
*/
/*
* @test id=POSIX_SPAWN
* @summary Check that we don't accumulate leaked FDs in the parent process
* @requires os.family == "linux"
* @library /test/lib
* @run main/othervm -Djdk.lang.Process.launchMechanism=posix_spawn NonPipelineLeaksFD
*/
public class NonPipelineLeaksFD {
final static int repeatCount = 50;
// Similar to PilelineLeaksFD, but where PilelineLeaksFD checks that the parent process
// does not leak file descriptors when invoking a pipeline, here we check that we don't
// leak FDs when executing simple (non-pipelined) programs but we test a wider span of
// redirection modes in both successful and failing variants.
// How this works:
//
// We execute a mix of failing and succeeding child process starts with various
// flavors of IO redirections many times; we observe the open file descriptors
// before and afterwards. Test fails if we have significantly more file descriptors
// open afterwards than before.
static int countNumberOfOpenFileDescriptors() {
return new File("/proc/self/fd").list().length;
}
static void printOpenFileDescriptors() {
long mypid = ProcessHandle.current().pid();
try(Process p = new ProcessBuilder("lsof", "-p", Long.toString(mypid))
.inheritIO().start()) {
p.waitFor();
} catch (InterruptedException | IOException ignored) {
// Quietly swallow; it was just an attempt.
}
}
static String readFirstLineOf(File f) {
String result;
try (BufferedReader b = new BufferedReader(new FileReader(f))){
result = b.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
return result;
}
static void runThisExpectSuccess(ProcessBuilder bld) {
try(Process p = bld.start()) {
p.waitFor();
if (p.exitValue() != 0) {
throw new RuntimeException("Unexpected exitcode");
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
static void runThisExpectError(ProcessBuilder bld) {
boolean failed = false;
try(Process p = bld.start()) {
p.waitFor();
} catch (IOException | InterruptedException e) {
failed = true;
}
if (!failed) {
throw new RuntimeException("Expected Error");
}
}
static void runPosWithPipes() {
ProcessBuilder bld = new ProcessBuilder("sh", "-c", "echo hallo");
runThisExpectSuccess(bld);
}
static void runPosWithInheritIO() {
ProcessBuilder bld = new ProcessBuilder("sh", "-c", "echo hallo").inheritIO();
runThisExpectSuccess(bld);
}
static void runPosWithRedirectToFile() {
File fo = new File("test.out");
ProcessBuilder bld = new ProcessBuilder("sh", "-c", "echo hallo");
bld.redirectOutput(ProcessBuilder.Redirect.to(fo));
runThisExpectSuccess(bld);
if (!readFirstLineOf(fo).equals("hallo")) {
throw new RuntimeException("mismatch");
}
}
static void runNegWithPipes() {
ProcessBuilder bld = new ProcessBuilder("doesnotexist");
runThisExpectError(bld);
}
static void runNegWithInheritIO() {
ProcessBuilder bld = new ProcessBuilder("doesnotexist").inheritIO();
runThisExpectError(bld);
}
static void runNegWithRedirectToFile() {
File fo = new File("test.out");
ProcessBuilder bld = new ProcessBuilder("doesnotexist");
bld.redirectOutput(ProcessBuilder.Redirect.to(fo));
runThisExpectError(bld);
}
static void doTestNTimesAndCountFDs(Runnable runnable, String name) {
System.out.println(name);
int c1 = countNumberOfOpenFileDescriptors();
for (int i = 0; i < repeatCount; i++) {
runnable.run();
}
int c2 = countNumberOfOpenFileDescriptors();
System.out.printf("%d->%d", c1, c2);
}
public static void main(String[] args) throws Exception {
System.out.println("jdk.lang.Process.launchMechanism=" +
System.getProperty("jdk.lang.Process.launchMechanism"));
int c1 = countNumberOfOpenFileDescriptors();
doTestNTimesAndCountFDs(NonPipelineLeaksFD::runPosWithPipes, "runPosWithPipes");
doTestNTimesAndCountFDs(NonPipelineLeaksFD::runPosWithInheritIO, "runPosWithInheritIO");
doTestNTimesAndCountFDs(NonPipelineLeaksFD::runPosWithRedirectToFile, "runPosWithRedirectToFile");
doTestNTimesAndCountFDs(NonPipelineLeaksFD::runNegWithPipes, "runNegWithPipes");
doTestNTimesAndCountFDs(NonPipelineLeaksFD::runNegWithInheritIO, "runNegWithInheritIO");
doTestNTimesAndCountFDs(NonPipelineLeaksFD::runNegWithRedirectToFile, "runNegWithRedirectToFile");
int c2 = countNumberOfOpenFileDescriptors();
System.out.printf("All tests: %d->%d", c1, c2);
printOpenFileDescriptors();
final int fudge = 10;
if (c2 > (c1 + fudge)) {
throw new RuntimeException(
String.format("Leak suspected (%d->%d) - see lsof output", c1, c2));
}
}
}

View file

@ -0,0 +1,244 @@
/*
* Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
/*
* @test id=FORK
* @bug 8289643 8291760 8291986
* @requires os.family == "mac" | os.family == "linux"
* @summary File descriptor leak detection with ProcessBuilder.startPipeline
* @library /test/lib
* @run junit/othervm/timeout=240 -Djdk.lang.Process.launchMechanism=FORK PipelineLeaksFD
*/
/*
* @test id=POSIX_SPAWN
* @bug 8289643 8291760 8291986 8379182
* @requires os.family == "mac" | os.family == "linux"
* @summary File descriptor leak detection with ProcessBuilder.startPipeline
* @library /test/lib
* @run junit/othervm/native/timeout=240 -Djdk.lang.Process.launchMechanism=POSIX_SPAWN PipelineLeaksFD
*/
public class PipelineLeaksFD {
private static final String TEST_JDK = System.getProperty("test.jdk");
private static final String JAVA_LIBRARY_PATH = System.getProperty("java.library.path");
private static final long MY_PID = ProcessHandle.current().pid();
// Maximum file descriptor to probe for being a pipe,
private static final int MAX_FD = 100;
// Test cases for pipelines with a number of pipeline sequences
public static Object[][] builders() {
return new Object[][]{
{List.of(new ProcessBuilder("cat"))},
{List.of(new ProcessBuilder("cat"),
new ProcessBuilder("cat"))},
{List.of(new ProcessBuilder("cat"),
new ProcessBuilder("cat"),
new ProcessBuilder("cat"),
new ProcessBuilder("cat"),
new ProcessBuilder("cat"))},
};
}
@ParameterizedTest
@MethodSource("builders")
void checkForLeaks(List<ProcessBuilder> builders) throws IOException {
System.out.println("Using:" + System.getProperty("jdk.lang.Process.launchMechanism"));
Set<PipeRecord> beforePipes = pipesFromSelf();
if (beforePipes.size() < 3) {
fail("There should be at least 3 pipes before, (0, 1, 2): is " +
beforePipes.size());
}
printPipes(beforePipes, "Before start");
List<Process> processes = ProcessBuilder.startPipeline(builders);
// Write something through the pipeline
final String text = "xyz";
try (Writer out = processes.getFirst().outputWriter()) {
out.write(text);
}
// Read, check, and close all streams
for (int i = 0; i < processes.size(); i++) {
final Process p = processes.get(i);
String expectedOut = (i == processes.size() - 1) ? text : null;
String expectedErr = null; // EOF
try (BufferedReader inputStream = p.inputReader();
BufferedReader errorStream = p.errorReader()) {
String outActual = inputStream.readLine();
assertEquals(expectedOut, outActual, "stdout, process[ " + i + "]: " + p);
String errActual = errorStream.readLine();
assertEquals(expectedErr, errActual, "stderr, process[ " + i + "]: " + p);
}
}
processes.forEach(PipelineLeaksFD::waitForQuiet);
Set<PipeRecord> afterPipes = pipesFromSelf();
if (!beforePipes.equals(afterPipes)) {
Set<PipeRecord> missing = new HashSet<>(beforePipes);
missing.removeAll(afterPipes);
printPipes(missing, "Missing from beforePipes()");
Set<PipeRecord> extra = new HashSet<>(afterPipes);
extra.removeAll(beforePipes);
printPipes(extra, "Extra pipes in afterPipes()");
fail("More or fewer pipes than expected");
}
}
// Test redirectErrorStream, both true and false
public static Object[][] redirectCases() {
return new Object[][] {
{true},
{false},
};
}
// Test redirectErrorStream (true/false) has the right number of pipes in use.
// Spawn the child to report its pipe inode info.
@ParameterizedTest()
@MethodSource("redirectCases")
void checkRedirectErrorStream(boolean redirectError) {
System.out.println("Using:" + System.getProperty("jdk.lang.Process.launchMechanism"));
try (Process p = new ProcessBuilder(TEST_JDK + "/bin/java",
"--enable-preview",
"-Djava.library.path=" + JAVA_LIBRARY_PATH,
"--enable-native-access=ALL-UNNAMED", "LinuxFDInfo")
.redirectErrorStream(redirectError)
.start()) {
final Set<PipeRecord> pipes = pipesFromSelf();
printPipes(pipes, "Parent pipes");
List<String> lines = p.inputReader().readAllLines();
Set<PipeRecord> childPipes = lines.stream().map(s -> {
var fdAndInode = LinuxFDInfo.parseFdAndInode(s);
return PipeRecord.lookup(fdAndInode.fd(), "0x%08x".formatted(fdAndInode.inode()), p.pid());
})
.collect(Collectors.toCollection(LinkedHashSet::new));
printPipes(childPipes, "child pipes");
int uniquePipes = childPipes.stream().map(PipeRecord::myKey).collect(Collectors.toSet()).size();
// Number of pipe references depending on whether stderr is redirected to stdout
long expected = redirectError ? 2 : 3;
assertEquals(expected, uniquePipes, "Wrong number of unique pipes");
String expectedTypeName = redirectError
? "java.lang.ProcessBuilder$NullInputStream"
: "java.lang.ProcessImpl$ProcessPipeInputStream";
assertEquals(expectedTypeName, p.getErrorStream().getClass().getName(),
"errorStream type is incorrect");
final Set<PipeRecord> afterPipes = pipesFromSelf();
if (!pipes.equals(afterPipes)) {
printPipes(afterPipes, "Parent pipes after are different");
}
} catch (IOException ioe) {
fail("Process start", ioe);
}
}
static void printPipes(Set<PipeRecord> pipes, String label) {
System.err.printf("%s: [%d]%n", label, pipes.size());
pipes.forEach(System.err::println);
}
static void waitForQuiet(Process p) {
try {
int st = p.waitFor();
if (st != 0) {
System.err.println("non-zero exit status: " + p);
}
} catch (InterruptedException ignore) {
}
}
static Set<PipeRecord> pipesFromSelf() {
Set<PipeRecord> pipes = new LinkedHashSet<>(MAX_FD);
for (int fd = 0; fd < MAX_FD; fd++) {
long inode = LinuxFDInfo.getPipeInodeNum(fd);
if (inode != 0) {
pipes.add(PipeRecord.lookup(fd, "0x%08x".formatted(inode), MY_PID));
}
}
return pipes;
}
// Identify a pipe by pid, fd, and a key (unique across processes)
// Mac OS X has separate keys for read and write sides, both are matched to the same "name"
record PipeRecord(long pid, int fd, KeyedString myKey) {
static PipeRecord lookup(int fd, String myKey, long pid) {
return new PipeRecord(pid, fd, KeyedString.getKey(myKey));
}
}
// A unique name for a string with a count of uses
// Used to associate pipe between parent and child.
static class KeyedString {
private static final HashMap<String, KeyedString> map = new HashMap<>();
private static int nextInt = 1;
private final String key;
private final String name;
KeyedString(String key, String name) {
this.key = key;
this.name = name;
}
KeyedString(String s) {
String k = "p" + nextInt++;
this(s, k);
}
static KeyedString getKey(String key) {
return map.computeIfAbsent(key, KeyedString::new);
}
public String toString() {
return name + ": osInfo: " + key;
}
}
}

View file

@ -0,0 +1,306 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
/*
* @test PipelineTest
* @bug 8211844
* @summary Tests for ProcessBuilder.startPipeline
*/
public class PipelineTest {
private static void realMain(String[] args) throws Throwable {
t1_simplePipeline();
t2_translatePipeline();
t3_redirectErrorStream();
t4_failStartPipeline();
}
/**
* Return a list of the varargs arguments.
* @param args elements to include in the list
* @param <T> the type of the elements
* @return a {@code List<T>} of the arguments
*/
@SafeVarargs
@SuppressWarnings("varargs")
static <T> List<T> asList(T... args) {
return Arrays.asList(args);
}
/**
* T1 - simple copy between two processes
*/
static void t1_simplePipeline() {
try {
String s1 = "Now is the time to check!";
verify(s1, s1,
asList(new ProcessBuilder("cat")));
verify(s1, s1,
asList(new ProcessBuilder("cat"),
new ProcessBuilder("cat")));
verify(s1, s1,
asList(new ProcessBuilder("cat"),
new ProcessBuilder("cat"),
new ProcessBuilder("cat")));
} catch (Throwable t) {
unexpected(t);
}
}
/**
* Pipeline that modifies the content.
*/
static void t2_translatePipeline() {
try {
String s2 = "Now is the time to check!";
String r2 = s2.replace('e', 'E').replace('o', 'O');
verify(s2, r2,
asList(new ProcessBuilder("tr", "e", "E"),
new ProcessBuilder("tr", "o", "O")));
} catch (Throwable t) {
unexpected(t);
}
}
/**
* Test that redirectErrorStream sends standard error of the first process
* to the standard output. The standard error of the first process should be empty.
* The standard output of the 2nd should contain the error message including the bad file name.
*/
static void t3_redirectErrorStream() {
try {
File p1err = new File("p1-test.err");
File p2out = new File("p2-test.out");
List<Process> processes = ProcessBuilder.startPipeline(
asList(new ProcessBuilder("cat", "NON-EXISTENT-FILE")
.redirectErrorStream(true)
.redirectError(p1err),
new ProcessBuilder("cat").redirectOutput(p2out)));
waitForAll(processes);
check("".equals(fileContents(p1err)), "The first process standard error should be empty");
String p2contents = fileContents(p2out);
check(p2contents.contains("NON-EXISTENT-FILE"),
"The error from the first process should be in the output of the second: " + p2contents);
} catch (Throwable t) {
unexpected(t);
}
}
/**
* Test that no processes are left after a failed startPipeline.
* Test illegal combinations of redirects.
*/
static void t4_failStartPipeline() {
File p1err = new File("p1-test.err");
File p2out = new File("p2-test.out");
THROWS(IllegalArgumentException.class,
() -> {
// Test that output redirect != PIPE throws IAE
List<Process> processes = ProcessBuilder.startPipeline(
asList(new ProcessBuilder("cat", "NON-EXISTENT-FILE1")
.redirectOutput(p1err),
new ProcessBuilder("cat")));
},
() -> {
// Test that input redirect != PIPE throws IAE
List<Process> processes = ProcessBuilder.startPipeline(
asList(new ProcessBuilder("cat", "NON-EXISTENT-FILE2"),
new ProcessBuilder("cat").redirectInput(p2out)));
}
);
THROWS(NullPointerException.class,
() -> {
List<Process> processes = ProcessBuilder.startPipeline(
asList(new ProcessBuilder("cat", "a"), null));
},
() -> {
List<Process> processes = ProcessBuilder.startPipeline(
asList(null, new ProcessBuilder("cat", "b")));
}
);
THROWS(IOException.class,
() -> {
List<Process> processes = ProcessBuilder.startPipeline(
asList(new ProcessBuilder("cat", "c"),
new ProcessBuilder("NON-EXISTENT-COMMAND")));
});
// Check no subprocess are left behind
ProcessHandle.current().children().forEach(PipelineTest::print);
ProcessHandle.current().children()
.filter(p -> p.info().command().orElse("").contains("cat"))
.forEach(p -> fail("process should have been destroyed: " + p));
}
static void verify(String input, String expected, List<ProcessBuilder> builders) throws IOException {
File infile = new File("test.in");
File outfile = new File("test.out");
setFileContents(infile, input);
for (int i = 0; i < builders.size(); i++) {
ProcessBuilder b = builders.get(i);
if (i == 0) {
b.redirectInput(infile);
}
if (i == builders.size() - 1) {
b.redirectOutput(outfile);
}
}
List<Process> processes = ProcessBuilder.startPipeline(builders);
verifyProcesses(processes);
waitForAll(processes);
String result = fileContents(outfile);
check(result.equals(expected),
"result not as expected: " + result + ", expected: " + expected);
}
/**
* Wait for each of the processes to be done.
*
* @param processes the list of processes to check
*/
static void waitForAll(List<Process> processes) {
processes.forEach(p -> {
try {
int status = p.waitFor();
} catch (InterruptedException ie) {
unexpected(ie);
}
});
}
static void print(ProcessBuilder pb) {
if (pb != null) {
System.out.printf(" pb: %s%n", pb);
System.out.printf(" cmd: %s%n", pb.command());
}
}
static void print(ProcessHandle p) {
System.out.printf("process: pid: %d, info: %s%n",
p.pid(), p.info());
}
// Check various aspects of the processes
static void verifyProcesses(List<Process> processes) {
for (int i = 0; i < processes.size(); i++) {
Process p = processes.get(i);
if (i != 0) {
verifyNullStream(p.getOutputStream(), "getOutputStream");
}
if (i <= processes.size() - 1) {
verifyNullStream(p.getInputStream(), "getInputStream");
}
if (i == processes.size() - 1) {
verifyNullStream(p.getErrorStream(), "getErrorStream");
}
}
}
static void verifyNullStream(OutputStream s, String msg) {
try {
s.write(0xff);
fail("Stream should have been a NullStream: " + msg);
} catch (IOException ie) {
// expected
}
}
static void verifyNullStream(InputStream s, String msg) {
try {
int len = s.read();
check(len == -1, "Stream should have been a NullStream: " + msg);
} catch (IOException ie) {
// expected
}
}
static void setFileContents(File file, String contents) {
try {
Writer w = new FileWriter(file);
w.write(contents);
w.close();
} catch (Throwable t) { unexpected(t); }
}
static String fileContents(File file) {
try {
Reader r = new FileReader(file);
StringBuilder sb = new StringBuilder();
char[] buffer = new char[1024];
int n;
while ((n = r.read(buffer)) != -1)
sb.append(buffer,0,n);
r.close();
return new String(sb);
} catch (Throwable t) { unexpected(t); return ""; }
}
//--------------------- Infrastructure ---------------------------
static volatile int passed = 0, failed = 0;
static void pass() {passed++;}
static void fail() {failed++; new Exception("Stack trace").printStackTrace(System.out);}
static void fail(String msg) {
System.out.println(msg); failed++;
new Exception("Stack trace: " + msg).printStackTrace(System.out);
}
static void unexpected(Throwable t) {failed++; t.printStackTrace(System.out);}
static void check(boolean cond) {if (cond) pass(); else fail();}
static void check(boolean cond, String m) {if (cond) pass(); else fail(m);}
static void equal(Object x, Object y) {
if (x == null ? y == null : x.equals(y)) pass();
else fail(">'" + x + "'<" + " not equal to " + "'" + y + "'");
}
public static void main(String[] args) throws Throwable {
try {realMain(args);} catch (Throwable t) {unexpected(t);}
System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);
if (failed > 0) throw new AssertionError("Some tests failed");
}
interface Fun {void f() throws Throwable;}
static void THROWS(Class<? extends Throwable> k, Fun... fs) {
for (Fun f : fs)
try { f.f(); fail("Expected " + k.getName() + " not thrown"); }
catch (Throwable t) {
if (k.isAssignableFrom(t.getClass())) pass();
else unexpected(t);}
}
}

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2026, IBM Corp.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test id=FORK
* @bug 8377907
* @summary Check that we don't open pipes without CLOEXCEC
* @requires os.family == "linux"
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/native -Djdk.lang.Process.launchMechanism=FORK PipesCloseOnExecTest
*/
/*
* @test id=VFORK
* @bug 8377907
* @summary Check that we don't open pipes without CLOEXCEC
* @requires os.family == "linux"
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/native -Djdk.lang.Process.launchMechanism=VFORK PipesCloseOnExecTest
*/
/*
* @test id=POSIX_SPAWN
* @bug 8377907
* @summary Check that we don't open pipes without CLOEXCEC
* @requires os.family == "linux"
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/native -Djdk.lang.Process.launchMechanism=POSIX_SPAWN PipesCloseOnExecTest
*/
import jdk.test.lib.process.OutputAnalyzer;
import java.io.IOException;
import java.time.LocalTime;
public class PipesCloseOnExecTest {
// How this works:
// - We start a child process A. Does not matter what, we just call "/bin/date".
// - Concurrently, we (natively, continuously) iterate all pipe file descriptors in
// the process and count all those that are not tagged with CLOEXEC. Finding one
// counts as error.
// Note that this test can only reliably succeed with Linux and the xxxBSDs, where
// we have pipe2(2).
//
// On MacOs and AIX, we emulate pipe2(2) with pipe(2) and fcntl(2); therefore we
// have a tiny time window in which a concurrent thread can can observe pipe
// filedescriptors without CLOEXEC. Furthermore, on MacOS, we also have to employ
// the double-dup-trick to workaround a the buggy MacOS implementation of posix_spawn.
// Therefore, on these platforms, the test would (correctly) spot "bad" file descriptors.
native static boolean startTester();
native static boolean stopTester();
static final int num_tries = 100;
static void printOpenFileDescriptors() {
long mypid = ProcessHandle.current().pid();
try(Process p = new ProcessBuilder("lsof", "-p", Long.toString(mypid))
.inheritIO().start()) {
p.waitFor();
} catch (InterruptedException | IOException ignored) {
// Quietly swallow; it was just an attempt.
}
}
public static void main(String[] args) throws Exception {
System.out.println("jdk.lang.Process.launchMechanism=" +
System.getProperty("jdk.lang.Process.launchMechanism"));
System.loadLibrary("PipesCloseOnExec");
if (!startTester()) {
throw new RuntimeException("Failed to start testers (see stdout)");
}
System.out.println(LocalTime.now() + ": Call ProcessBuilder.start...");
for (int i = 0; i < num_tries; i ++) {
ProcessBuilder pb = new ProcessBuilder("true").inheritIO();
new OutputAnalyzer(pb.start()).shouldHaveExitValue(0);
}
if (!stopTester()) {
printOpenFileDescriptors();
throw new RuntimeException("Catched FDs without CLOEXEC? Check output.");
}
}
}

View file

@ -0,0 +1,198 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2026, IBM Corp.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <jni.h>
#include "testlib_thread_barriers.h"
static void trc(const char* fmt, ...) {
char buf [1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
printf("%s\n", buf);
fflush(stdout);
}
/* Set 1 to restrict this test to pipes, 0 to test all file descriptors.
* (for now, we ignore regular files opened with CLOEXEC since loaded jars seem not tagged as CLOEXEc.
* We should probably fix that eventually. */
#define TEST_PIPES_ONLY 1
/* stdin/out/err file descriptors are usually not CLOEXEC */
#define IGNORE_BELOW 4
/* Only query file descriptors up to this point */
#define MAX_FD 1024
static pthread_t tid_tester;
static pthread_barrier_t start_barrier;
static atomic_bool stop_now = false;
/* Mainly to prevent tracing the same fd over and over again:
* 1 - present, 2 - present, cloexec */
static unsigned fd_state[MAX_FD];
static bool is_pipe(int fd) {
struct stat mystat;
if (fstat(fd, &mystat) == -1) {
return false;
}
return mystat.st_mode & S_IFIFO;
}
static void print_fd_details(int fd, char* out, size_t outlen) {
const char* type = "unknown";
char link[1024] = { 0 };
char procfd[129];
struct stat mystat;
out[0] = '\0';
if (fstat(fd, &mystat) == -1) {
snprintf(out, outlen, "%s", errno == EBADF ? "EBADF" : "???");
return;
}
switch (mystat.st_mode & S_IFMT) {
case S_IFBLK: type = "blk"; break;
case S_IFCHR: type = "char"; break;
case S_IFDIR: type = "dir"; break;
case S_IFIFO: type = "fifo"; break;
case S_IFLNK: type = "lnk"; break;
case S_IFREG: type = "reg"; break;
case S_IFSOCK: type = "sock"; break;
}
snprintf(procfd, sizeof(procfd) - 1, "/proc/self/fd/%d", fd);
int linklen = readlink(procfd, link, sizeof(link) - 1);
if (linklen > 0) {
link[linklen] = '\0';
snprintf(out, outlen, "%s (%s)", type, link);
} else {
snprintf(out, outlen, "%s", type);
}
}
/* Returns true for error */
static bool testFD(int fd) {
int rc = fcntl(fd, F_GETFD);
if (rc == -1) {
return false;
}
const bool has_cloexec = (rc & FD_CLOEXEC);
const bool is_a_pipe = is_pipe(fd);
const unsigned state = has_cloexec ? 2 : 1;
bool had_error = false;
if (fd_state[fd] != state) {
fd_state[fd] = state;
char buf[1024];
print_fd_details(fd, buf, sizeof(buf));
if (has_cloexec) {
trc("%d: %s", fd, buf);
} else {
if (fd < IGNORE_BELOW) {
trc("%d: %s ** CLOEXEC MISSING ** (ignored - below scanned range)", fd, buf);
} else if (TEST_PIPES_ONLY && !is_a_pipe) {
trc("%d: %s ** CLOEXEC MISSING ** (ignored - not a pipe)", fd, buf);
} else {
trc("%d: %s ** CLOEXEC MISSING ** (ERROR)", fd, buf);
had_error = true;
}
}
}
return had_error;
}
static void* testerLoop(void* dummy) {
pthread_barrier_wait(&start_barrier);
trc("Tester is alive");
bool had_error = false;
while (!atomic_load(&stop_now)) {
for (int fd = 0; fd < MAX_FD; fd++) {
bool rc = testFD(fd);
had_error = had_error || rc;
}
}
trc("Tester dies");
return had_error ? (void*)1 : NULL;
}
JNIEXPORT jboolean JNICALL
Java_PipesCloseOnExecTest_startTester(JNIEnv* env, jclass cls)
{
pthread_attr_t attr;
int rc = 0;
if (pthread_barrier_init(&start_barrier, NULL, 2) != 0) {
trc("pthread_barrier_init failed (%d)", errno);
return false;
}
pthread_attr_init(&attr);
if (pthread_create(&tid_tester, &attr, testerLoop, NULL) != 0) {
trc("pthread_create failed (%d)", errno);
return JNI_FALSE;
}
pthread_barrier_wait(&start_barrier);
trc("Started tester");
return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL
Java_PipesCloseOnExecTest_stopTester(JNIEnv* env, jclass cls)
{
atomic_store(&stop_now, true);
void* retval = NULL;
pthread_join(tid_tester, &retval);
pthread_barrier_destroy(&start_barrier);
return retval == NULL ? JNI_TRUE : JNI_FALSE;
}

View file

@ -0,0 +1,8 @@
############################################################
# Enable logging java.lang.ProcessBuilder to the console
############################################################
handlers= java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = ALL
java.lang.ProcessBuilder.level = FINE

View file

@ -0,0 +1,8 @@
############################################################
# Enable logging java.lang.ProcessBuilder to the console
############################################################
handlers= java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = ALL
java.lang.ProcessBuilder.level = FINER

View file

@ -0,0 +1,8 @@
############################################################
# Enable logging java.lang.ProcessBuilder to the console
############################################################
handlers= java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = ALL
java.lang.ProcessBuilder.level = INFO

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary verify that Process Reaper threads have a null CCL
* @bug 8269488
* @run junit ProcessReaperCCL
*/
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class ProcessReaperCCL {
@Test
void test() throws Exception {
// create a class loader
File dir = new File(".");
URL[] urls = new URL[] {dir.toURI().toURL()};
ClassLoader cl = new URLClassLoader(urls);
Thread.currentThread().setContextClassLoader(cl);
// Invoke a subprocess with processBuilder
ProcessBuilder pb = new ProcessBuilder(List.of("echo", "abc", "xyz"));
Process p = pb.start();
CompletableFuture<Process> cf = p.onExit();
int exitValue = cf.get().exitValue();
Assertions.assertEquals(0, exitValue, "error exit value");
// Verify all "Process Reaper" threads have a null CCL
for (Thread th : Thread.getAllStackTraces().keySet()) {
if (th.getName().startsWith("process reaper")) {
Assertions.assertNull(th.getContextClassLoader(), "CCL not null");
}
}
}
}

View file

@ -0,0 +1,189 @@
/*
* Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.StreamHandler;
import java.util.stream.Stream;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.ParameterizedTest;
import static org.junit.jupiter.api.Assertions.*;
/*
* @test
* @summary verify logging of ProcessBuilder.start()
* @requires vm.flagless
* @run junit/othervm ProcessStartLoggingTest
*/
public class ProcessStartLoggingTest {
private final static int NORMAL_STATUS = 0;
private final static int ERROR_STATUS = 1;
private static final String TEST_JDK = System.getProperty("test.jdk");
private static final String TEST_SRC = System.getProperty("test.src");
private static Object HOLD_LOGGER;
/**
* Launch a process with the arguments.
* @param args 1 or strings passed directly to ProcessBuilder as command and arguments.
*/
public static void main(String[] args) throws InterruptedException {
if (System.getProperty("ThrowingHandler") != null) {
HOLD_LOGGER = ProcessStartLoggingTest.ThrowingHandler.installHandler();
}
String directory = System.getProperty("directory");
try {
ProcessBuilder pb = new ProcessBuilder(args);
pb.directory((directory == null) ? null : new File(directory));
Process p = pb.start();
int status = p.waitFor();
if (status != 0) {
System.out.println("exitValue: " + status);
}
} catch (IOException ioe) {
System.out.println("ProcessBuilder.start() threw IOException: " + ioe);
}
}
/**
* Test various log level settings, and none.
* @return a stream of arguments for parameterized test
*/
private static Stream<Arguments> logParamProvider() {
return Stream.of(
// Logging enabled with level TRACE
Arguments.of(List.of("-Djava.util.logging.config.file=" +
Path.of(TEST_SRC, "ProcessLogging-FINER.properties").toString(),
"-Ddirectory=."),
List.of("echo", "echo0"),
NORMAL_STATUS,
"dir: ., cmd: \"echo\" \"echo0\""),
// Logging enabled with level DEBUG
Arguments.of(List.of("-Djava.util.logging.config.file=" +
Path.of(TEST_SRC, "ProcessLogging-FINE.properties").toString(),
"-Ddirectory=."),
List.of("echo", "echo1"),
NORMAL_STATUS,
"dir: ., cmd: \"echo\""),
// Logging disabled due to level INFO
Arguments.of(List.of("-Djava.util.logging.config.file=" +
Path.of(TEST_SRC, "ProcessLogging-INFO.properties").toString()),
List.of("echo", "echo2"),
NORMAL_STATUS,
""),
// Console logger DEBUG
Arguments.of(List.of("--limit-modules", "java.base",
"-Djdk.system.logger.level=DEBUG"),
List.of("echo", "echo3"),
NORMAL_STATUS,
"dir: null, cmd: \"echo\""),
// Console logger TRACE
Arguments.of(List.of("--limit-modules", "java.base",
"-Djdk.system.logger.level=TRACE",
"-Ddirectory=."),
List.of("echo", "echo4"),
NORMAL_STATUS,
"dir: ., cmd: \"echo\" \"echo4\""),
// No Logging configured
Arguments.of(List.of(),
List.of("echo", "echo5"),
NORMAL_STATUS,
""),
// Throwing Handler
Arguments.of(List.of("-DThrowingHandler",
"-Djava.util.logging.config.file=" +
Path.of(TEST_SRC, "ProcessLogging-FINE.properties").toString()),
List.of("echo", "echo6"),
ERROR_STATUS,
"Exception in thread \"main\" java.lang.RuntimeException: Exception in publish")
);
}
/**
* Check that the logger output of a launched process contains the expected message.
*
* @param logArgs Arguments to configure logging in the java test process
* @param childArgs the args passed to the child to be invoked as a Process
* @param expectMessage log should contain the message
*/
@ParameterizedTest
@MethodSource("logParamProvider")
public void checkLogger(List<String> logArgs, List<String> childArgs,
int expectedStatus, String expectMessage) {
ProcessBuilder pb = new ProcessBuilder();
pb.redirectErrorStream(true);
List<String> cmd = pb.command();
cmd.add(Path.of(TEST_JDK,"bin", "java").toString());
cmd.addAll(logArgs);
cmd.add(this.getClass().getName());
cmd.addAll(childArgs);
try {
Process process = pb.start();
try (BufferedReader reader = process.inputReader()) {
List<String> lines = reader.lines().toList();
boolean match = (expectMessage.isEmpty())
? lines.size() == 0
: lines.stream().filter(s -> s.contains(expectMessage)).findFirst().isPresent();
if (!match) {
// Output lines for debug
System.err.println("Expected> \"" + expectMessage + "\"");
lines.forEach(l -> System.err.println("Actual> \"" + l+ "\""));
fail("Unexpected log contents");
}
}
int result = process.waitFor();
assertEquals(expectedStatus, result, "Exit status");
} catch (IOException | InterruptedException ex) {
fail(ex);
}
}
/**
* A LoggingHandler that throws an Exception.
*/
public static class ThrowingHandler extends StreamHandler {
// Install this handler for java.lang.ProcessBuilder
public static Logger installHandler() {
Logger logger = Logger.getLogger("java.lang.ProcessBuilder");
logger.addHandler(new ProcessStartLoggingTest.ThrowingHandler());
return logger;
}
@Override
public synchronized void publish(LogRecord record) {
throw new RuntimeException("Exception in publish");
}
}
}

View file

@ -0,0 +1,365 @@
/*
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import static org.junit.jupiter.api.Assertions.*;
import jdk.test.lib.process.ProcessTools;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Files;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.stream.Stream;
import jtreg.SkippedException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/*
* @test
* @requires vm.flagless
* @library /test/lib
* @build jdk.test.lib.process.ProcessTools
* @run junit ReaderWriterTest
*/
public class ReaderWriterTest {
static final String ASCII = "ASCII: \u0000_A-Z_a-Z_\u007C_\u007D_\u007E_\u007F_;";
static final String ISO_8859_1 = " Symbols: \u00AB_\u00BB_\u00fc_\u00fd_\u00fe_\u00ff;";
static final String FRACTIONS = " Fractions: \u00bc_\u00bd_\u00be_\u00bf;";
public static final String TESTCHARS = "OneWay: " + ASCII + ISO_8859_1 + FRACTIONS;
public static final String ROUND_TRIP_TESTCHARS = "RoundTrip: " + ASCII + ISO_8859_1 + FRACTIONS;
static Stream<Arguments> charsetCases() {
return Stream.of(
Arguments.of("UTF-8"),
Arguments.of("ISO8859-1"),
Arguments.of("US-ASCII")
);
}
/**
* Test the defaults case of native.encoding. No extra command line flags or switches.
*/
@Test
void testCaseNativeEncoding() throws IOException {
String nativeEncoding = System.getProperty("native.encoding");
Charset cs = Charset.forName(nativeEncoding);
System.out.println("Native.encoding Charset: " + cs);
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder("ReaderWriterTest$ChildWithCharset");
Process p = pb.start();
writeTestChars(p.outputWriter());
checkReader(p.inputReader(), cs, "Out");
checkReader(p.errorReader(), cs, "Err");
try {
int exitValue = p.waitFor();
if (exitValue != 0)
System.out.println("exitValue: " + exitValue);
} catch (InterruptedException ie) {
fail("waitFor interrupted");
}
}
/**
* Test that redirects of input and error streams result in Readers that are empty.
* Test that when the output to a process is redirected, the writer acts as
* a null stream and throws an exception as expected for a null output stream
* as specified by ProcessBuilder.
*/
@Test
void testRedirects() throws IOException {
String nativeEncoding = System.getProperty("native.encoding");
Charset cs = Charset.forName(nativeEncoding);
System.out.println("Native.encoding Charset: " + cs);
Path inPath = Path.of("InFile.tmp");
BufferedWriter inWriter = Files.newBufferedWriter(inPath);
inWriter.close();
Path outPath = Path.of("OutFile.tmp");
Path errorPath = Path.of("ErrFile.tmp");
for (int errType = 1; errType < 4; errType++) {
// Three cases to test for which the error stream is empty
// 1: redirectErrorStream(false); redirect of errorOutput to a file
// 2: redirectErrorStream(true); no redirect of errorOutput
// 3: redirectErrorStream(true); redirect of errorOutput to a file
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder("ReaderWriterTest$ChildWithCharset");
pb.redirectInput(inPath.toFile());
pb.redirectOutput(outPath.toFile());
if (errType == 1 || errType == 3) {
pb.redirectError(errorPath.toFile());
}
if (errType == 2 || errType == 3) {
pb.redirectErrorStream(true);
}
try (Process p = pb.start()) {
// Output has been redirected to a null stream; success is IOException on the write
BufferedWriter wr = p.outputWriter();
assertThrows(IOException.class, () -> {
wr.write("X");
wr.flush();
});
// InputReader should be empty; and at EOF
BufferedReader inputReader = p.inputReader();
int ch = inputReader.read();
assertEquals(-1, ch, "inputReader not at EOF: ch: " + (char) ch);
// InputReader should be empty; and at EOF
BufferedReader errorReader = p.errorReader();
ch = errorReader.read();
assertEquals(-1, ch, "errorReader not at EOF: ch: " + (char) ch);
int exitValue = p.waitFor();
if (exitValue != 0) System.out.println("exitValue: " + exitValue);
} catch (InterruptedException ie) {
fail("waitFor interrupted");
}
}
}
/**
* Write the test characters to the child using the Process.outputWriter.
* @param writer the Writer
* @throws IOException if an I/O error occurs
*/
private static void writeTestChars(Writer writer) throws IOException {
// Write the test data to the child
try (writer) {
writer.append(ROUND_TRIP_TESTCHARS);
writer.append(System.lineSeparator());
}
}
/**
* Test a child with a character set.
* A Process is spawned; characters are written to and read from the child
* using the character set and compared.
*
* @param encoding a charset name
*/
@ParameterizedTest
@MethodSource("charsetCases")
void testCase(String encoding) throws IOException {
Charset cs = null;
try {
cs = Charset.forName(encoding);
System.out.println("Charset: " + cs);
} catch (UnsupportedCharsetException use) {
throw new SkippedException("Charset not supported: " + encoding);
}
String cleanCSName = cleanCharsetName(cs);
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-Dsun.stdout.encoding=" + cleanCSName, // Encode in the child using the charset
"-Dsun.stderr.encoding=" + cleanCSName,
"ReaderWriterTest$ChildWithCharset");
Process p = pb.start();
// Write the test data to the child
writeTestChars(p.outputWriter(cs));
checkReader(p.inputReader(cs), cs, "Out");
checkReader(p.errorReader(cs), cs, "Err");
try {
int exitValue = p.waitFor();
if (exitValue != 0)
System.out.println("exitValue: " + exitValue);
} catch (InterruptedException ie) {
}
}
/**
* Test passing null when a charset is expected
* @throws IOException if an I/O error occurs; not expected
*/
@Test
void testNullCharsets() throws IOException {
// Launch a child; its behavior is not interesting and is ignored
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"ReaderWriterTest$ChildWithCharset");
try (Process p = pb.start()) {
assertThrows(NullPointerException.class, () -> writeTestChars(p.outputWriter(null)));
assertThrows(NullPointerException.class, () -> checkReader(p.inputReader(null), null, "Out"));
assertThrows(NullPointerException.class, () -> checkReader(p.errorReader(null), null, "Err"));
}
}
/**
* Test passing different charset on multiple calls when the same charset is expected.
* @throws IOException if an I/O error occurs; not expected
*/
@Test
void testIllegalArgCharsets() throws IOException {
String nativeEncoding = System.getProperty("native.encoding");
Charset cs = Charset.forName(nativeEncoding);
System.out.println("Native.encoding Charset: " + cs);
Charset otherCharset = cs.equals(StandardCharsets.UTF_8)
? StandardCharsets.ISO_8859_1
: StandardCharsets.UTF_8;
// Launch a child; its behavior is not interesting and is ignored
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"ReaderWriterTest$ChildWithCharset");
Process p = pb.start();
try {
var writer = p.outputWriter(cs);
writer = p.outputWriter(cs); // try again with same
writer = p.outputWriter(otherCharset); // this should throw
fail("Process.outputWriter(otherCharset) did not throw IllegalStateException");
} catch (IllegalStateException ile) {
// expected, ignore
System.out.println(ile);
}
try {
var reader = p.inputReader(cs);
reader = p.inputReader(cs); // try again with same
reader = p.inputReader(otherCharset); // this should throw
fail("Process.inputReader(otherCharset) did not throw IllegalStateException");
} catch (IllegalStateException ile) {
// expected, ignore
System.out.println(ile);
}
try {
var reader = p.errorReader(cs);
reader = p.errorReader(cs); // try again with same
reader = p.errorReader(otherCharset); // this should throw
fail("Process.errorReader(otherCharset) did not throw IllegalStateException");
} catch (IllegalStateException ile) {
// expected, ignore
System.out.println(ile);
}
p.destroyForcibly();
try {
// Collect the exit status to cleanup after the process; but ignore it
p.waitFor();
} catch (InterruptedException ie) {
// Ignored
}
}
private static void checkReader(BufferedReader reader, Charset cs, String label) throws IOException {
try (BufferedReader in = reader) {
String prefix = " " + label + ": ";
String firstline = in.readLine();
System.out.append(prefix).println(firstline);
String secondline = in.readLine();
System.out.append(prefix).println(secondline);
for (String line = in.readLine(); line != null; line = in.readLine()) {
System.out.append(prefix).append(line);
System.out.println();
}
ByteBuffer bb = cs.encode(TESTCHARS);
String reencoded = cs.decode(bb).toString();
if (!firstline.equals(reencoded))
diffStrings(firstline, reencoded);
assertEquals(reencoded, firstline, label + " Test Chars");
bb = cs.encode(ROUND_TRIP_TESTCHARS);
reencoded = cs.decode(bb).toString();
if (!secondline.equals(reencoded))
diffStrings(secondline, reencoded);
assertEquals(reencoded, secondline, label + " Round Trip Test Chars");
}
}
/**
* A cleaned up Charset name that is suitable for Linux LANG environment variable.
* If there are two '-'s the first one is removed.
* @param cs a Charset
* @return the cleanedup Charset name
*/
private static String cleanCharsetName(Charset cs) {
String name = cs.name();
int ndx = name.indexOf('-');
if (ndx >= 0 && name.indexOf('-', ndx + 1) >= 0) {
name = name.substring(0, ndx) + name.substring(ndx + 1);
}
return name;
}
private static void diffStrings(String actual, String expected) {
if (actual.equals(expected))
return;
int lenDiff = expected.length() - actual.length();
if (lenDiff != 0)
System.out.println("String lengths: " + actual.length() + " != " + expected.length());
int first; // find first mismatched character
for (first = 0; first < Math.min(actual.length(), expected.length()); first++) {
if (actual.charAt(first) != expected.charAt(first))
break;
}
int last;
for (last = actual.length() - 1; last >= 0 && (last + lenDiff) >= 0; last--) {
if (actual.charAt(last) != expected.charAt(last + lenDiff))
break; // last mismatched character
}
System.out.printf("actual vs expected[%3d]: 0x%04x != 0x%04x%n", first, (int)actual.charAt(first), (int)expected.charAt(first));
System.out.printf("actual vs expected[%3d]: 0x%04x != 0x%04x%n", last, (int)actual.charAt(last), (int)expected.charAt(last));
System.out.printf("actual [%3d-%3d]: %s%n", first, last, actual.substring(first, last+1));
System.out.printf("expected[%3d-%3d]: %s%n", first, last, expected.substring(first, last + lenDiff + 1));
}
static class ChildWithCharset {
public static void main(String[] args) {
String nativeEncoding = System.getProperty("native.encoding");
System.out.println(TESTCHARS);
byte[] bytes = null;
try {
bytes = System.in.readAllBytes();
System.out.write(bytes); // echo bytes back to parent on stdout
} catch (IOException ioe) {
ioe.printStackTrace(); // Seen by the parent
}
System.out.println("native.encoding: " + nativeEncoding);
System.out.println("sun.stdout.encoding: " + System.getProperty("sun.stdout.encoding"));
System.out.println("LANG: " + System.getenv().get("LANG"));
System.err.println(TESTCHARS);
try {
System.err.write(bytes); // echo bytes back to parent on stderr
} catch (IOException ioe) {
ioe.printStackTrace(); // Seen by the parent
}
System.err.println("native.encoding: " + nativeEncoding);
System.err.println("sun.stderr.encoding: " + System.getProperty("sun.stderr.encoding"));
}
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2015 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8072611
* @requires (os.family == "windows")
* @summary ProcessBuilder Redirect to file appending on Windows should work with long file names
* @author Thomas Stuefe
*/
import java.io.File;
import java.lang.ProcessBuilder.Redirect;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class RedirectWithLongFilename {
public static void main(String[] args) throws Exception {
// Redirect ProcessBuilder output to a file whose pathlen is > 255.
Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir"));
File dir2 = null;
File longFileName = null;
try {
dir2 = Files.createTempDirectory(tmpDir, "RedirectWithLongFilename").toFile();
dir2.mkdirs();
longFileName = new File(dir2,
"012345678901234567890123456789012345678901234567890123456789" +
"012345678901234567890123456789012345678901234567890123456789" +
"012345678901234567890123456789012345678901234567890123456789" +
"012345678901234567890123456789012345678901234567890123456789" +
"0123456789");
ProcessBuilder pb = new ProcessBuilder("hostname.exe");
pb.redirectOutput(Redirect.appendTo(longFileName));
Process p = pb.start();
p.waitFor();
if (longFileName.exists()) {
System.out.println("OK");
} else {
throw new RuntimeException("Test failed.");
}
} finally {
longFileName.delete();
dir2.delete();
}
}
}

View file

@ -0,0 +1,228 @@
/*
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 6921885
* @run main/othervm SiblingIOEHandle
* @summary inherit IOE handles and MS CreateProcess limitations (kb315939)
* @requires (os.family == "windows")
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class SiblingIOEHandle {
private static enum APP {
A, B, C;
}
private static File stopC = new File(".\\StopCs.txt");
private static String SIGNAL = "B child reported.";
private static String JAVA_EXE = System.getProperty("java.home")
+ File.separator + "bin"
+ File.separator + "java";
private static String[] getCommandArray(String processName) {
String[] cmdArray = {
JAVA_EXE,
"-cp",
System.getProperty("java.class.path"),
SiblingIOEHandle.class.getName(),
processName
};
return cmdArray;
}
public static void main(String[] args) {
APP app = (args.length > 0) ? APP.valueOf(args[0]) : APP.A;
switch (app) {
case A:
performA(true);
performA(false);
break;
case B:
performB();
break;
case C:
performC();
break;
}
}
static boolean procClaunched = false;
private static void waitAbit() {
try {
Thread.sleep(0);
} catch (InterruptedException ex) {
// that was long enough
}
}
private static boolean waitBarrier(CyclicBarrier barrier) {
while (true) try {
barrier.await();
return true;
} catch (InterruptedException ex) {
continue;
} catch (BrokenBarrierException ex) {
ex.printStackTrace();
return false;
}
}
private static class ProcessC implements Runnable {
private CyclicBarrier barrier;
private Process processC;
public ProcessC(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
if (waitBarrier(barrier)) {
waitAbit();
// Run process C next to B ASAP to make an attempt
// to capture the B-process IOE handles in C process.
ProcessBuilder builderC = new ProcessBuilder(
getCommandArray(APP.C.name()));
processC = builderC.start();
procClaunched = true;
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void waitFor() throws InterruptedException {
processC.waitFor();
}
}
private static void performA(boolean fileOut) {
try {
stopC.delete();
ProcessBuilder builderB = new ProcessBuilder(
getCommandArray(APP.B.name()));
File outB = null;
if (fileOut) {
outB = new File("outB.txt");
builderB.redirectOutput(outB);
}
builderB.redirectErrorStream(true);
final CyclicBarrier barrier = new CyclicBarrier(2);
//Create process C in a new thread
ProcessC processC = new ProcessC(barrier);
Thread procCRunner = new Thread(processC);
procCRunner.start();
if (!waitBarrier(barrier)) {
throw new RuntimeException("Catastrophe in process A! Synchronization failed.");
}
// Run process B first.
Process processB = builderB.start();
while (true) try {
procCRunner.join();
break;
} catch (InterruptedException ex) {
continue;
}
if (!procClaunched) {
throw new RuntimeException("Catastrophe in process A! C was not launched.");
}
processB.getOutputStream().close();
processB.getErrorStream().close();
if (fileOut) {
try {
processB.waitFor();
} catch (InterruptedException ex) {
throw new RuntimeException("Catastrophe in process B! B hung up.");
}
System.err.println("Trying to delete [outB.txt].");
if (!outB.delete()) {
throw new RuntimeException("Greedy brother C deadlock! File share.");
}
System.err.println("Succeeded in delete [outB.txt].");
} else {
System.err.println("Read stream start.");
boolean isSignalReceived = false;
try (BufferedReader in = new BufferedReader(new InputStreamReader(
processB.getInputStream(), "utf-8"))) {
String result;
while ((result = in.readLine()) != null) {
if (SIGNAL.equals(result)) {
isSignalReceived = true;
} else {
throw new RuntimeException("Catastrophe in process B! Bad output.");
}
}
}
if (!isSignalReceived) {
throw new RuntimeException("Signal from B was not received");
}
System.err.println("Read stream finished.");
}
// If JDK-6921885 is not fixed that point is unreachable.
// Test timeout exception.
// write signal file to stop C process.
stopC.createNewFile();
processC.waitFor();
} catch (IOException ex) {
throw new RuntimeException("Catastrophe in process A!", ex);
} catch (InterruptedException ex) {
throw new RuntimeException("Process A was interrupted while waiting for C", ex);
}
}
private static void performB() {
System.out.println(SIGNAL);
}
private static void performC() {
// If JDK-7147084 is not fixed the loop is 5min long.
for (int i = 0; i < 5 * 60; ++i) {
try {
Thread.sleep(1000);
// check for sucess
if (stopC.exists())
break;
} catch (InterruptedException ex) {
// that is ok. Longer sleep - better effect.
}
}
}
}

View file

@ -0,0 +1,176 @@
/*
* Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8155808
* @requires vm.flagless
* @run main SkipTest
* @summary verify skip method of Process Input Stream
*/
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class SkipTest {
private static final String javaExe =
System.getProperty("java.home") +
File.separator + "bin" + File.separator + "java";
private static final String classpath =
System.getProperty("java.class.path");
static final int BLOCK_SIZE = 10000;
public static void main(String[] args) throws Throwable {
// Start a Process to generate the test data to stdout and stderr
ProcessBuilder pb = new ProcessBuilder(javaExe, "-classpath", classpath, "SkipTest$GenerateData");
System.out.printf("cmd: %s%n", pb.command());
Process process = pb.start();
/*
* Verify the data expected is received mixing reads and skip.
*/
try (InputStream in = process.getInputStream()) {
// Note: Process.getInputStream() actually returns a BufferedInputStream whose
// skip() method works perfectly, partially obscuring the bug. Only when the
// BufferedInputStream's buffer is drained, and it passes the skip() call to
// the underlying native stream, does the problem occur.
long n = in.skip(-1);
if (n != 0) {
throw new AssertionError("skip(-1) should return 0");
}
n = in.skip(0);
if (n != 0) {
throw new AssertionError("skip(0) should return 0");
}
// Now iterate all the data blocks
int header;
for (int expectedHeader = 'A'; (header = in.read()) != -1; expectedHeader++) {
// The header byte should be simple 'A' to 'Z'.
// When the bug hits, we will get lowercase letters instead.
if (header != expectedHeader) {
throw new AssertionError("header char wrong, expected: " +
expectedHeader + ", actual: " + header);
}
// Handle the data bytes.
// If the correct number of bytes are not skipped,
// then subsequent reads become out-of-sync;
int remaining = BLOCK_SIZE;
do {
remaining -= in.skip(remaining);
} while (remaining != 0);
}
n = in.skip(1);
if (n != 0) {
throw new AssertionError("skip(1) at eof should return 0");
}
}
/**
* Do the same for the standard error stream.
*/
try (InputStream in = process.getErrorStream()) {
long n = in.skip(-1);
if (n != 0) {
throw new AssertionError("skip(-1) should return 0");
}
n = in.skip(0);
if (n != 0) {
throw new AssertionError("skip(0) should return 0");
}
// Now iterate all the data blocks
int header;
for (int expectedHeader = 'A'; (header = in.read()) != -1; expectedHeader++) {
// The header byte should be simple 'A' to 'Z'.
// When the bug hits, we will get lowercase letters instead.
if (header != expectedHeader) {
throw new AssertionError("header char wrong, expected: " +
expectedHeader + ", actual: " + header);
}
// Handle the data bytes.
// If the correct number of bytes are not skipped,
// then subsequent reads become out-of-sync;
int remaining = BLOCK_SIZE;
do {
remaining -= in.skip(remaining);
} while (remaining != 0);
}
n = in.skip(1);
if (n != 0) {
throw new AssertionError("skip(1) at eof should return 0");
}
}
}
/**
* Alternate main to generate the test data to standard output
* and standard error.
*/
static class GenerateData {
public static void main(String[] args) {
// Generate test data containing a series of data blocks of length BLOCK_SIZE,
// each with a one-byte header. For example's sake, the "header" is a capital letter,
// and the "data" is the lowercase version of that letter repeated BLOCK_SIZE times:
try (OutputStream out = new BufferedOutputStream(System.out)) {
for (int header = 'A'; header <= 'Z'; header++) {
out.write(header);
int data = Character.toLowerCase(header);
for (int i = 0; i < BLOCK_SIZE; i++) {
out.write(data);
}
}
} catch (IOException ioe) {
ioe.printStackTrace();
System.exit(1);
}
// Generate the same data to the error output
try (OutputStream err = new BufferedOutputStream(System.err)) {
for (int header = 'A'; header <= 'Z'; header++) {
err.write(header);
int data = Character.toLowerCase(header);
for (int i = 0; i < BLOCK_SIZE; i++) {
err.write(data);
}
}
} catch (IOException ioe) {
ioe.printStackTrace();
System.exit(1);
}
}
}
}

View file

@ -0,0 +1 @@
maxOutputSize=12000000

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
/*
* @test id=posix_spawn
* @summary Verify Signal mask is cleared by ProcessBuilder start when using posix_spawn mode
* @bug 8234262
* @requires (os.family == "linux" | os.family == "mac")
* @comment Don't allow -Xcomp, it disturbs the relative timing of the sleep and kill commands
* @requires (vm.compMode != "Xcomp")
* @run main/othervm/timeout=480 -Djdk.lang.Process.launchMechanism=POSIX_SPAWN UnblockSignals
* @run main/othervm/timeout=480 -Djdk.lang.Process.launchMechanism=POSIX_SPAWN -Xrs UnblockSignals
*/
/*
* @test id=fork
* @summary Verify Signal mask is cleared by ProcessBuilder start when using fork mode
* @bug 8357683
* @requires (os.family == "linux" | os.family == "mac")
* @comment Don't allow -Xcomp, it disturbs the relative timing of the sleep and kill commands
* @requires (vm.compMode != "Xcomp")
* @run main/othervm/timeout=480 -Djdk.lang.Process.launchMechanism=FORK UnblockSignals
* @run main/othervm/timeout=480 -Djdk.lang.Process.launchMechanism=FORK -Xrs UnblockSignals
*/
public class UnblockSignals {
public static void main(String[] args) throws IOException, InterruptedException {
// Check that SIGQUIT is not masked, in previous releases it was masked
final ProcessBuilder pb = new ProcessBuilder("sleep", "30").inheritIO();
Process p = pb.start();
System.out.printf("Child %d, %s%n", p.pid(), pb.command());
ProcessBuilder killpb = new ProcessBuilder("kill", "-s", "QUIT", Long.toString(p.pid()));
Process killp = killpb.start();
System.out.printf("Child %d, %s%n", killp.pid(), killpb.command());
int killst = killp.waitFor();
if (killst != 0) {
throw new RuntimeException("Kill process failed, exit status: " + killst);
}
int sleepStatus = p.waitFor();
if (sleepStatus == 0) {
throw new RuntimeException("Child not killed");
}
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Verify that when a child returns exit value 259, Process.waitFor does not throw
* @bug 8294899
* @requires (os.family == "windows")
* @run junit WindowsExitValue
*/
import java.io.IOException;
import org.junit.*;
import static org.junit.Assert.*;
public class WindowsExitValue {
@Test
public void checkExit259() throws IOException, InterruptedException {
Process process = new ProcessBuilder("cmd", "/c", "exit /b 259").start();
int exitValue = process.waitFor();
assertEquals(exitValue, 259);
}
}

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2006, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @run main/othervm Zombies
* @bug 6474073 6180151
* @summary Make sure zombies don't get created on Unix
* @author Martin Buchholz
*/
import java.io.*;
public class Zombies {
static final String os = System.getProperty("os.name");
static final String TrueCommand = os.contains("OS X")?
"/usr/bin/true" : "/bin/true";
public static void main(String[] args) throws Throwable {
if (! new File("/usr/bin/perl").canExecute() ||
! new File("/bin/ps").canExecute())
return;
System.out.println("Looks like a Unix system.");
long mypid = ProcessHandle.current().pid();
System.out.printf("mypid: %d%n", mypid);
final Runtime rt = Runtime.getRuntime();
try {
String[] cmd = {"no-such-file"};
rt.exec(cmd);
throw new Error("expected IOException not thrown");
} catch (IOException expected) {/* OK */}
try {
String[] cmd = {"."};
rt.exec(cmd);
throw new Error("expected IOException not thrown");
} catch (IOException expected) {/* OK */}
try {
String[] cmd = {TrueCommand};
rt.exec(cmd, null, new File("no-such-dir"));
throw new Error("expected IOException not thrown");
} catch (IOException expected) {/* OK */}
Process p = rt.exec(TrueCommand);
ProcessHandle pp = p.toHandle().parent().orElse(null);
System.out.printf("%s pid: %d, parent: %s%n", TrueCommand, p.pid(), pp);
p.waitFor();
// Count all the zombies that are children of this Java process
final String[] zombieCounter = {
"/usr/bin/perl", "-e",
"$a=`/bin/ps -eo ppid,pid,s,command`;" +
"print @b=$a=~/^ *@{[getppid]} +[0-9]+ +Z.*$/mog;" +
"exit @b"
};
ProcessBuilder pb = new ProcessBuilder(zombieCounter);
pb.inheritIO();
int zombies = pb.start().waitFor();
if (zombies != 0) {
throw new Error(zombies + " zombies!");
}
}
}

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ProcessHandle;
import jdk.test.lib.util.FileUtils;
/*
* @test
* @bug 8239893
* @summary Verify that handles for processes that terminate do not accumulate
* @requires ((os.family == "windows") & (vm.compMode != "Xcomp"))
* @library /test/lib
* @run main/othervm/native -Xint CheckHandles
*/
public class CheckHandles {
public static void main(String[] args) throws Exception {
System.out.println("mypid: " + ProcessHandle.current().pid());
// Warmup the process launch mechanism and vm to stabilize the number of handles in use
int MAX_WARMUP = 20;
long prevCount = FileUtils.getProcessHandleCount();
for (int i = 0; i < MAX_WARMUP; i++) {
oneProcess();
System.gc(); // an opportunity to close unreferenced handles
sleep(10);
long count = FileUtils.getProcessHandleCount();
if (count < 0)
throw new AssertionError("getProcessHandleCount failed");
System.out.println("warmup handle delta: " + (count - prevCount));
prevCount = count;
}
System.out.println("Warmup done");
System.out.println();
prevCount = FileUtils.getProcessHandleCount();
long startHandles = prevCount;
long maxHandles = startHandles;
int MAX_SPAWN = 50;
for (int i = 0; i < MAX_SPAWN; i++) {
oneProcess();
System.gc(); // an opportunity to close unreferenced handles
sleep(10);
long count = FileUtils.getProcessHandleCount();
if (count < 0)
throw new AssertionError("getProcessHandleCount failed");
System.out.println("handle delta: " + (count - prevCount));
prevCount = count;
maxHandles = Math.max(maxHandles, count);
}
System.out.println("Processes started: " + MAX_SPAWN);
System.out.println("startHandles: " + startHandles);
System.out.println("maxHandles: " + maxHandles);
final float ERROR_PERCENT = 10.0f; // allowable extra handles
final long ERROR_THRESHOLD = startHandles + Math.round(startHandles * ERROR_PERCENT / 100.0f);
if (maxHandles >= ERROR_THRESHOLD) {
throw new AssertionError("Handle use increased by more than " + ERROR_PERCENT + " percent.");
}
}
/**
* Start a single process and consume its output.
*/
private static void oneProcess() {
try {
Process testProcess = new ProcessBuilder("cmd", "/c", "dir").start();
Thread outputConsumer = new Thread(() -> consumeStream(testProcess.getInputStream()));
outputConsumer.setDaemon(true);
outputConsumer.start();
Thread errorConsumer = new Thread(() -> consumeStream(testProcess.getErrorStream()));
errorConsumer.setDaemon(true);
errorConsumer.start();
testProcess.waitFor();
outputConsumer.join();
errorConsumer.join();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
throw new RuntimeException("Exception", e);
}
}
private static void consumeStream(InputStream inputStream) {
BufferedReader reader = null;
try {
int lines = 0;
reader = new BufferedReader(new InputStreamReader(inputStream));
while (reader.readLine() != null) {
lines++;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ie) {
// ignore
}
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test id=posix_spawn
* @bug 8364611
* @summary Check that childs start with SIG_DFL as SIGPIPE disposition
* @requires os.family != "windows"
* @library /test/lib
* @run main/othervm/native -Djdk.lang.Process.launchMechanism=posix_spawn -agentlib:ChangeSignalDisposition TestChildSignalDisposition
*/
/**
* @test id=fork
* @bug 8364611
* @summary Check that childs start with SIG_DFL as SIGPIPE disposition
* @requires os.family != "windows"
* @library /test/lib
* @run main/othervm/native -Djdk.lang.Process.launchMechanism=fork -agentlib:ChangeSignalDisposition TestChildSignalDisposition
*/
/**
* @test id=vfork
* @bug 8364611
* @summary Check that childs start with SIG_DFL as SIGPIPE disposition
* @requires os.family == "linux"
* @library /test/lib
* @run main/othervm/native -Djdk.lang.Process.launchMechanism=vfork -agentlib:ChangeSignalDisposition TestChildSignalDisposition
*/
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
public class TestChildSignalDisposition {
// This test has two native parts:
// - a library injected into the JVM with -agentlib changes signal disposition of the VM process for SIGPIPE to
// SIG_IGN
// - a small native executable that prints out, in its main function, all signal handler dispositions, to be executed
// as a child process.
//
// What should happen: In child process, SIGPIPE should be set to default.
public static void main(String[] args) throws Exception {
ProcessBuilder pb = ProcessTools.createNativeTestProcessBuilder("PrintSignalDisposition");
OutputAnalyzer output = ProcessTools.executeProcess(pb);
output.shouldHaveExitValue(0);
output.shouldNotMatch("SIGPIPE: +ignore");
output.shouldNotMatch("SIGPIPE: +block");
output.shouldMatch("SIGPIPE: +default");
output.reportDiagnosticSummary();
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <errno.h>
#include "jvmti.h"
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static const struct { int sig; const char* name; } signals[] = {
{ SIGABRT, "SIGABRT" }, { SIGALRM, "SIGALRM" }, { SIGBUS, "SIGBUS" }, { SIGCHLD, "SIGCHLD" }, { SIGCONT, "SIGCONT" },
{ SIGFPE, "SIGFPE" }, { SIGHUP, "SIGHUP" }, { SIGILL, "SIGILL" }, { SIGINT, "SIGINT" }, { SIGKILL, "SIGKILL" },
{ SIGPIPE, "SIGPIPE" }, { SIGQUIT, "SIGQUIT" }, { SIGSEGV, "SIGSEGV" }, { SIGSTOP, "SIGSTOP" }, { SIGTERM, "SIGTERM" },
{ SIGTSTP, "SIGTSTP" }, { SIGTTIN, "SIGTTIN" }, { SIGTTOU, "SIGTTOU" }, { SIGUSR1, "SIGUSR1" }, { SIGUSR2, "SIGUSR2" },
#ifdef SIGPOLL
{ SIGPOLL, "SIGPOLL" },
#endif
{ SIGPROF, "SIGPROF" }, { SIGSYS, "SIGSYS" }, { SIGTRAP, "SIGTRAP" }, { SIGURG, "SIGURG" }, { SIGVTALRM, "SIGVTALRM" },
{ SIGXCPU, "SIGXCPU" }, { SIGXFSZ, "SIGXFSZ" }, { -1, NULL }
};
int main(int argc, char** argv) {
printf("PID: %d\n", getpid());
sigset_t current_mask;
sigemptyset(&current_mask);
if (sigprocmask(SIG_BLOCK /* ignored */, NULL, &current_mask) != 0) {
printf("sigprocmask %d\n", errno);
return -1;
}
for (int n = 0; signals[n].sig != -1; n++) {
printf("%s: ", signals[n].name);
if (sigismember(&current_mask, signals[n].sig)) {
printf("blocked ");
}
struct sigaction act;
if (sigaction(signals[n].sig, NULL, &act) != 0) {
printf("sigaction %d\n", errno);
printf("\n");
continue;
}
const void* const handler = (act.sa_flags & SA_SIGINFO ?
(void*)act.sa_sigaction : (void*)act.sa_handler);
if (handler == (void*)SIG_DFL) {
printf("default ");
} else if (handler == (void*)SIG_IGN) {
printf("ignore ");
} else if (handler == (void*)SIG_HOLD) {
printf("hold ");
} else {
printf("%p ", handler);
}
printf("%X\n", act.sa_flags);
}
return 0;
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <errno.h>
#include "jvmti.h"
#include <signal.h>
#include <stdio.h>
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
if (signal(SIGPIPE, SIG_IGN) != SIG_ERR) {
printf("changed signal disposition for SIGPIPE to SIG_IGN\n");
} else {
printf("FAILED to change signal disposition for SIGPIPE to SIG_IGN (%d)\n", errno);
return JNI_ERR;
}
return JNI_OK;
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
/**
* Command line program to sleep at least given number of seconds.
* The behavior should equivalent to the Unix sleep command.
* Actual time sleeping may vary if interrupted, the remaining time
* returned from sleep has limited accuracy.
*
* Note: the file name prefix "exe" identifies the source should be built into BasicSleep(.exe).
*/
int main(int argc, char** argv) {
int seconds;
if (argc < 2 || (seconds = atoi(argv[1])) < 0) {
fprintf(stderr, "usage: BasicSleep <non-negative seconds>\n");
exit(1);
}
#ifdef _WIN32
Sleep(seconds * 1000);
#else
while ((seconds = sleep(seconds)) > 0) {
// until no more to sleep
}
#endif
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni.h"
#include <sys/stat.h>
// If the file descriptor is a PIPE, return the INode.
JNIEXPORT jlong JNICALL
Java_LinuxFDInfo_getPipeInodeNum(JNIEnv *env, jclass cls, jint fd) {
#ifdef S_ISFIFO
struct stat buf;
if (fstat(fd, &buf) != -1 && S_ISFIFO(buf.st_mode)) {
return buf.st_ino;
}
#endif
return 0;
}