1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import static java.io.File.createTempFile; 25 import static java.lang.Long.parseLong; 26 import static java.lang.System.getProperty; 27 import static java.nio.file.Files.readAllBytes; 28 import static java.util.Arrays.stream; 29 import static java.util.stream.Collectors.joining; 30 import static java.util.stream.Collectors.toList; 31 import static jdk.test.lib.process.ProcessTools.createJavaProcessBuilder; 32 33 import java.io.BufferedReader; 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.io.InputStreamReader; 39 import java.util.Collection; 40 import java.util.Optional; 41 import java.util.stream.Stream; 42 43 /* 44 * @test TestInheritFD 45 * @bug 8176717 8176809 46 * @summary a new process should not inherit open file descriptors 47 * @library /test/lib 48 * @modules java.base/jdk.internal.misc 49 * java.management 50 */ 51 52 /** 53 * Test that HotSpot does not leak logging file descriptors. 54 * 55 * This test is performed in three steps. The first VM starts a second VM with 56 * gc logging enabled. The second VM starts a third VM and redirects the third 57 * VMs output to the first VM, it then exits and hopefully closes its log file. 58 * 59 * The third VM waits for the second to exit and close its log file. After that, 60 * the third VM tries to rename the log file of the second VM. If it succeeds in 61 * doing so it means that the third VM did not inherit the open log file 62 * (windows can not rename opened files easily) 63 * 64 * The third VM communicates the success to rename the file by printing "CLOSED 65 * FD". The first VM checks that the string was printed by the third VM. 66 * 67 * On unix like systems "lsof" or "pfiles" is used. 68 */ 69 70 public class TestInheritFD { 71 72 public static final String LEAKS_FD = "VM RESULT => LEAKS FD"; 73 public static final String RETAINS_FD = "VM RESULT => RETAINS FD"; 74 public static final String EXIT = "VM RESULT => VM EXIT"; 75 public static final String LOG_SUFFIX = ".strangelogsuffixthatcanbecheckedfor"; 76 77 // first VM 78 public static void main(String[] args) throws Exception { 79 String logPath = createTempFile("logging", LOG_SUFFIX).getName(); 80 File commFile = createTempFile("communication", ".txt"); 81 82 if (isWindows() == false && lsofCommand().isPresent() == false) { 83 System.out.println("Could not find lsof like command"); 84 System.out.println("Exit test case as successful though it could not verify anything"); 85 return; 86 } 87 88 ProcessBuilder pb = createJavaProcessBuilder( 89 "-Xlog:gc:\"" + logPath + "\"", 90 "-Dtest.jdk=" + getProperty("test.jdk"), 91 VMStartedWithLogging.class.getName(), 92 logPath); 93 94 pb.redirectOutput(commFile); // use temp file to communicate between processes 95 pb.start(); 96 97 String out = ""; 98 do { 99 out = new String(readAllBytes(commFile.toPath())); 100 Thread.sleep(100); 101 System.out.println("SLEEP 100 millis"); 102 } while (!out.contains(EXIT)); 103 104 System.out.println(out); 105 if (out.contains(RETAINS_FD)) { 106 System.out.println("Log file was not inherited by third VM"); 107 } else { 108 throw new RuntimeException("could not match: " + RETAINS_FD); 109 } 110 } 111 112 static class VMStartedWithLogging { 113 // second VM 114 public static void main(String[] args) throws IOException, InterruptedException { 115 ProcessBuilder pb = createJavaProcessBuilder( 116 "-Dtest.jdk=" + getProperty("test.jdk"), 117 VMShouldNotInheritFileDescriptors.class.getName(), 118 args[0], 119 "" + ProcessHandle.current().pid()); 120 pb.inheritIO(); // in future, redirect information from third VM to first VM 121 pb.start(); 122 123 if (!isWindows()) { 124 System.out.println("(Second VM) Open file descriptors:\n" + outputContainingFilenames().stream().collect(joining("\n"))); 125 } 126 } 127 } 128 129 static class VMShouldNotInheritFileDescriptors { 130 // third VM 131 public static void main(String[] args) throws InterruptedException { 132 try { 133 File logFile = new File(args[0]); 134 long parentPid = parseLong(args[1]); 135 fakeLeakyJVM(false); // for debugging of test case 136 137 if (isWindows()) { 138 windows(logFile, parentPid); 139 } else { 140 Collection<String> output = outputContainingFilenames(); 141 System.out.println("(Third VM) Open file descriptors:\n" + output.stream().collect(joining("\n"))); 142 System.out.println(findOpenLogFile(output) ? LEAKS_FD : RETAINS_FD); 143 } 144 } catch (Exception e) { 145 System.out.println(e.toString()); 146 } finally { 147 System.out.println(EXIT); 148 } 149 } 150 } 151 152 // for debugging of test case 153 @SuppressWarnings("resource") 154 static void fakeLeakyJVM(boolean fake) { 155 if (fake) { 156 try { 157 new FileOutputStream("fakeLeakyJVM" + LOG_SUFFIX, false); 158 } catch (FileNotFoundException e) { 159 } 160 } 161 } 162 163 static Stream<String> run(String... args){ 164 try { 165 return new BufferedReader(new InputStreamReader(new ProcessBuilder(args).start().getInputStream())).lines(); 166 } catch (IOException e) { 167 throw new RuntimeException(e); 168 } 169 } 170 171 static boolean isWindows() { 172 return getProperty("os.name").toLowerCase().contains("win"); 173 } 174 175 static Optional<String[]> lsofCommandCache = stream(new String[][]{ 176 {"/usr/bin/lsof", "-p"}, 177 {"/usr/sbin/lsof", "-p"}, 178 {"/bin/lsof", "-p"}, 179 {"/sbin/lsof", "-p"}, 180 {"/usr/local/bin/lsof", "-p"}, 181 {"/usr/bin/pfiles", "-F"}}) // Solaris 182 .filter(args -> new File(args[0]).exists()) 183 .findFirst(); 184 185 static Optional<String[]> lsofCommand() { 186 return lsofCommandCache; 187 } 188 189 static Collection<String> outputContainingFilenames() { 190 long pid = ProcessHandle.current().pid(); 191 192 String[] command = lsofCommand().orElseThrow(() -> new RuntimeException("lsof like command not found")); 193 System.out.println("using command: " + command[0] + " " + command[1]); 194 return run(command[0], command[1], "" + pid).collect(toList()); 195 } 196 197 static boolean findOpenLogFile(Collection<String> fileNames) { 198 return fileNames.stream() 199 .filter(fileName -> fileName.contains(LOG_SUFFIX)) 200 .findAny() 201 .isPresent(); 202 } 203 204 static void windows(File f, long parentPid) throws InterruptedException { 205 System.out.println("waiting for pid: " + parentPid); 206 ProcessHandle.of(parentPid).ifPresent(handle -> handle.onExit().join()); 207 System.out.println("trying to rename file to the same name: " + f); 208 System.out.println(f.renameTo(f) ? RETAINS_FD : LEAKS_FD); // this parts communicates a closed file descriptor by printing "VM RESULT => RETAINS FD" 209 }