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