8379122: Test java/lang/ProcessBuilder/Basic.java fails with 'Exception: java.lang.Error: PATH search algorithm" for multi-call binaries

Reviewed-by: jpai, bpb, stuefe
This commit is contained in:
Roger Riggs 2026-04-30 13:35:25 +00:00
parent d88d476e7b
commit bdf95baebf
3 changed files with 123 additions and 24 deletions

View File

@ -64,7 +64,7 @@ import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import static java.lang.System.getenv;
import static java.lang.System.out;
import static java.lang.Boolean.TRUE;
@ -686,21 +686,28 @@ public class Basic {
// On Alpine Linux, /bin/true and /bin/false are just links to /bin/busybox.
// Some tests copy /bin/true and /bin/false to files with a different filename.
// However, copying the busbox executable into a file with a different name
// However, copying the busybox executable into a file with a different name
// won't result in the expected return codes. As workaround, we create
// executable files that can be copied and produce the expected return
// native executables that can be copied and produce the expected return
// values.
private static class TrueExe {
public static String path() { return path; }
private static final Path BIN_TRUE_PATH = Paths.get("/bin/true");
private static final boolean IS_TRUE_FALSE_SAFE_TO_USE = isTrueFalseSafeToUse();
private static final String path = path0();
private static String path0(){
if (!Files.isSymbolicLink(Paths.get("/bin/true"))) {
return "/bin/true";
private static String path0() {
if (IS_TRUE_FALSE_SAFE_TO_USE) {
return BIN_TRUE_PATH.toString();
} else {
File trueExe = new File("true");
setFileContents(trueExe, "#!/bin/true\n");
trueExe.setExecutable(true);
File trueExe = findReplacementCommand("BasicTrue");
if (trueExe == null) {
// Fall back to a script that invokes /bin/true
trueExe = new File("true");
setFileContents(trueExe, "#!/bin/true\n");
trueExe.setExecutable(true);
}
return trueExe.getAbsolutePath();
}
}
@ -708,19 +715,39 @@ public class Basic {
private static class FalseExe {
public static String path() { return path; }
private static final Path BIN_FALSE_PATH = Paths.get("/bin/false");
private static final String path = path0();
private static String path0(){
if (!Files.isSymbolicLink(Paths.get("/bin/false"))) {
return "/bin/false";
private static String path0() {
if (TrueExe.IS_TRUE_FALSE_SAFE_TO_USE) {
return BIN_FALSE_PATH.toString();
} else {
File falseExe = new File("false");
setFileContents(falseExe, "#!/bin/false\n");
falseExe.setExecutable(true);
File falseExe = findReplacementCommand("BasicFalse");
if (falseExe == null) {
// Fall back to a script that invokes /bin/false
falseExe = new File("false");
setFileContents(falseExe, "#!/bin/false\n");
falseExe.setExecutable(true);
}
return falseExe.getAbsolutePath();
}
}
}
// Check if /bin/true and /bin/false are ok to use as is.
// Neither can be a symbolic link or the same binary as the other.
private static boolean isTrueFalseSafeToUse() {
try {
if (Files.isSymbolicLink(TrueExe.BIN_TRUE_PATH) ||
Files.isSymbolicLink(FalseExe.BIN_FALSE_PATH)) {
return false;
}
return Files.isSameFile(TrueExe.BIN_TRUE_PATH, FalseExe.BIN_FALSE_PATH);
} catch (IOException ioe) {
return false;
}
}
static class EnglishUnix {
private static final Boolean is =
(! Windows.is() && isEnglish("LANG") && isEnglish("LC_ALL"));
@ -1983,6 +2010,7 @@ public class Basic {
// PATH search algorithm on Unix
//----------------------------------------------------------------
try {
System.out.printf("Paths: True: %s, False: %s\n", TrueExe.path(), FalseExe.path());
List<String> childArgs = new ArrayList<String>(javaChildArgs);
childArgs.add("PATH search algorithm");
ProcessBuilder pb = new ProcessBuilder(childArgs);
@ -2556,26 +2584,37 @@ public class Basic {
private static final String TEST_NATIVEPATH = System.getProperty("test.nativepath");
// Path where "sleep" program may be found" or null
private static final Path SLEEP_PATH = initSleepPath();
private static final File SLEEP_PATH = initSleepPath();
/**
* Compute the Path to a sleep executable.
* @return a Path to sleep or BasicSleep(.exe) or null if none
* @return a path to sleep or BasicSleep(.exe) or null if none
*/
private static Path initSleepPath() {
if (Windows.is() && TEST_NATIVEPATH != null) {
// exeBasicSleep is equivalent to sleep on Unix
Path exePath = Path.of(TEST_NATIVEPATH).resolve("BasicSleep.exe");
private static File initSleepPath() {
return Windows.is() ? findReplacementCommand("BasicSleep")
: findSystemCommand("sleep");
}
/// Search the NativePath for the command.
/// On Windows, include the .exe extension
private static File findReplacementCommand(String cmdName) {
if (TEST_NATIVEPATH != null) {
String fullName = cmdName + (Windows.is() ? ".exe" : "");
Path exePath = Path.of(TEST_NATIVEPATH).resolve(fullName);
if (Files.isExecutable(exePath)) {
return exePath;
return exePath.toFile();
}
}
return null;
}
/// Search the usual system paths for the command and return the full Path.
private static File findSystemCommand(String cmdName) {
List<String> binPaths = List.of("/bin", "/usr/bin");
for (String dir : binPaths) {
Path exePath = Path.of(dir).resolve("sleep");
Path exePath = Path.of(dir).resolve(cmdName);
if (Files.isExecutable(exePath)) {
return exePath;
return exePath.toFile();
}
}
return null;

View File

@ -0,0 +1,30 @@
/*
* 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.
*/
/**
* Command line program to return exit status 1.
* Note: the file name prefix "exe" identifies the source should be built into BasicFalse(.exe).
*/
int main(int argc, char** argv) {
return 1;
}

View File

@ -0,0 +1,30 @@
/*
* 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.
*/
/**
* Command line program to return exit status 0.
* Note: the file name prefix "exe" identifies the source should be built into BasicTrue(.exe).
*/
int main(int argc, char** argv) {
return 0;
}