From bdf95baebfb9f4aeb4c44767fdf1a4c746217f8e Mon Sep 17 00:00:00 2001 From: Roger Riggs Date: Thu, 30 Apr 2026 13:35:25 +0000 Subject: [PATCH] 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 --- test/jdk/java/lang/ProcessBuilder/Basic.java | 87 ++++++++++++++----- .../java/lang/ProcessBuilder/exeBasicFalse.c | 30 +++++++ .../java/lang/ProcessBuilder/exeBasicTrue.c | 30 +++++++ 3 files changed, 123 insertions(+), 24 deletions(-) create mode 100644 test/jdk/java/lang/ProcessBuilder/exeBasicFalse.c create mode 100644 test/jdk/java/lang/ProcessBuilder/exeBasicTrue.c diff --git a/test/jdk/java/lang/ProcessBuilder/Basic.java b/test/jdk/java/lang/ProcessBuilder/Basic.java index b150690ef48..f8f04753c26 100644 --- a/test/jdk/java/lang/ProcessBuilder/Basic.java +++ b/test/jdk/java/lang/ProcessBuilder/Basic.java @@ -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 childArgs = new ArrayList(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 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; diff --git a/test/jdk/java/lang/ProcessBuilder/exeBasicFalse.c b/test/jdk/java/lang/ProcessBuilder/exeBasicFalse.c new file mode 100644 index 00000000000..1dfb30d7487 --- /dev/null +++ b/test/jdk/java/lang/ProcessBuilder/exeBasicFalse.c @@ -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; +} diff --git a/test/jdk/java/lang/ProcessBuilder/exeBasicTrue.c b/test/jdk/java/lang/ProcessBuilder/exeBasicTrue.c new file mode 100644 index 00000000000..dd5282cbd46 --- /dev/null +++ b/test/jdk/java/lang/ProcessBuilder/exeBasicTrue.c @@ -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; +}