/* * 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. */ import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; import java.util.List; import java.util.Set; /* * @test * @bug 8289643 8291760 * @requires (os.family == "linux" & !vm.musl) * @summary File descriptor leak detection with ProcessBuilder.startPipeline * @run testng/othervm PipelineLeaksFD */ @Test public class PipelineLeaksFD { @DataProvider public 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"))}, }; } @Test(dataProvider = "builders") void checkForLeaks(List builders) throws IOException { Set pipesBefore = myPipes(); if (pipesBefore.size() < 3) { System.out.println(pipesBefore); Assert.fail("There should be at least 3 pipes before, (0, 1, 2)"); } List processes = ProcessBuilder.startPipeline(builders); // Write something through the pipeline final String text = "xyz"; try (Writer out = processes.get(0).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(); Assert.assertEquals(outActual, expectedOut, "stdout, process[ " + i + "]: " + p); String errActual = errorStream.readLine(); Assert.assertEquals(errActual, expectedErr, "stderr, process[ " + i + "]: " + p); } } processes.forEach(p -> waitForQuiet(p)); Set pipesAfter = myPipes(); if (!pipesBefore.equals(pipesAfter)) { Set missing = new HashSet<>(pipesBefore); missing.removeAll(pipesAfter); printPipes(missing, "Missing from pipesAfter"); Set extra = new HashSet<>(pipesAfter); extra.removeAll(pipesBefore); printPipes(extra, "Extra pipes in pipesAfter"); Assert.fail("More or fewer pipes than expected"); } } static void printPipes(Set pipes, String label) { System.out.printf("%s: [%d]%n", label, pipes.size()); pipes.forEach(r -> System.out.printf("%-20s: %s%n", r.fd(), r.link())); } static void waitForQuiet(Process p) { try { int st = p.waitFor(); if (st != 0) { System.out.println("non-zero exit status: " + p); } } catch (InterruptedException ie) { } } /** * Collect a Set of pairs of /proc fd paths and the symbol links that are pipes. * @return A set of PipeRecords, possibly empty */ static Set myPipes() { Path path = Path.of("/proc/" + ProcessHandle.current().pid() + "/fd"); Set pipes = new HashSet<>(); File[] files = path.toFile().listFiles(f -> Files.isSymbolicLink(f.toPath())); if (files != null) { for (File file : files) { try { Path link = Files.readSymbolicLink(file.toPath()); if (link.toString().startsWith("pipe:")) { pipes.add(new PipeRecord(file.toPath(), link)); } } catch (IOException ioe) { } } } return pipes; } record PipeRecord(Path fd, Path link) { }; }