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