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