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 import static jdk.test.lib.Platform.isWindows;
  33 
  34 import java.io.BufferedReader;
  35 import java.io.File;
  36 import java.io.FileNotFoundException;
  37 import java.io.FileOutputStream;
  38 import java.io.IOException;
  39 import java.io.InputStreamReader;
  40 import java.util.Collection;
  41 import java.util.Optional;
  42 import java.util.stream.Stream;
  43 
  44 /*
  45  * @test TestInheritFD
  46  * @bug 8176717 8176809
  47  * @summary a new process should not inherit open file descriptors
  48  * @comment On Aix lsof requires root privileges.
  49  * @requires os.family != "aix"
  50  * @library /test/lib
  51  * @modules java.base/jdk.internal.misc
  52  *          java.management
  53  */
  54 
  55 /**
  56  * Test that HotSpot does not leak logging file descriptors.
  57  *
  58  * This test is performed in three steps. The first VM starts a second VM with
  59  * gc logging enabled. The second VM starts a third VM and redirects the third
  60  * VMs output to the first VM, it then exits and hopefully closes its log file.
  61  *
  62  * The third VM waits for the second to exit and close its log file. After that,
  63  * the third VM tries to rename the log file of the second VM. If it succeeds in
  64  * doing so it means that the third VM did not inherit the open log file
  65  * (windows can not rename opened files easily)
  66  *
  67  * The third VM communicates the success to rename the file by printing "CLOSED
  68  * FD". The first VM checks that the string was printed by the third VM.
  69  *
  70  * On unix like systems "lsof" or "pfiles" is used.
  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         if (!isWindows() && !lsofCommand().isPresent()) {
  86             System.out.println("Could not find lsof like command");
  87             System.out.println("Exit test case as successful though it could not verify anything");
  88             return;
  89         }
  90 
  91         ProcessBuilder pb = createJavaProcessBuilder(
  92             "-Xlog:gc:\"" + logPath + "\"",
  93             "-Dtest.jdk=" + getProperty("test.jdk"),
  94             VMStartedWithLogging.class.getName(),
  95             logPath);
  96 
  97         pb.redirectOutput(commFile); // use temp file to communicate between processes
  98         pb.start();
  99 
 100         String out = "";
 101         do {
 102             out = new String(readAllBytes(commFile.toPath()));
 103             Thread.sleep(100);
 104             System.out.println("SLEEP 100 millis");
 105         } while (!out.contains(EXIT));
 106 
 107         System.out.println(out);
 108         if (out.contains(RETAINS_FD)) {
 109             System.out.println("Log file was not inherited by third VM");
 110         } else {
 111             throw new RuntimeException("could not match: " + RETAINS_FD);
 112         }
 113     }
 114 
 115     static class VMStartedWithLogging {
 116         // second VM
 117         public static void main(String[] args) throws IOException, InterruptedException {
 118             ProcessBuilder pb = createJavaProcessBuilder(
 119                 "-Dtest.jdk=" + getProperty("test.jdk"),
 120                 VMShouldNotInheritFileDescriptors.class.getName(),
 121                 args[0],
 122                 "" + ProcessHandle.current().pid());
 123             pb.inheritIO(); // in future, redirect information from third VM to first VM
 124             pb.start();
 125 
 126             if (!isWindows()) {
 127                 System.out.println("(Second VM) Open file descriptors:\n" + outputContainingFilenames().stream().collect(joining("\n")));
 128             }
 129         }
 130     }
 131 
 132     static class VMShouldNotInheritFileDescriptors {
 133         // third VM
 134         public static void main(String[] args) throws InterruptedException {
 135             try {
 136                 File logFile = new File(args[0]);
 137                 long parentPid = parseLong(args[1]);
 138                 fakeLeakyJVM(false); // for debugging of test case
 139 
 140                 if (isWindows()) {
 141                     windows(logFile, parentPid);
 142                 } else {
 143                     Collection<String> output = outputContainingFilenames();
 144                     System.out.println("(Third VM) Open file descriptors:\n" + output.stream().collect(joining("\n")));
 145                     System.out.println(findOpenLogFile(output) ? LEAKS_FD : RETAINS_FD);
 146                 }
 147             } catch (Exception e) {
 148                 System.out.println(e.toString());
 149             } finally {
 150                 System.out.println(EXIT);
 151             }
 152         }
 153     }
 154 
 155     // for debugging of test case
 156     @SuppressWarnings("resource")
 157     static void fakeLeakyJVM(boolean fake) {
 158         if (fake) {
 159             try {
 160                 new FileOutputStream("fakeLeakyJVM" + LOG_SUFFIX, false);
 161             } catch (FileNotFoundException e) {
 162             }
 163         }
 164     }
 165 
 166     static Stream<String> run(String... args){
 167         try {
 168             return new BufferedReader(new InputStreamReader(new ProcessBuilder(args).start().getInputStream())).lines();
 169         } catch (IOException e) {
 170             throw new RuntimeException(e);
 171         }
 172     }
 173 
 174     static Optional<String[]> lsofCommandCache = stream(new String[][]{
 175             {"/usr/bin/lsof", "-p"},
 176             {"/usr/sbin/lsof", "-p"},
 177             {"/bin/lsof", "-p"},
 178             {"/sbin/lsof", "-p"},
 179             {"/usr/local/bin/lsof", "-p"},
 180             {"/usr/bin/pfiles", "-F"}}) // Solaris
 181         .filter(args -> new File(args[0]).exists())
 182         .findFirst();
 183 
 184     static Optional<String[]> lsofCommand() {
 185         return lsofCommandCache;
 186     }
 187 
 188     static Collection<String> outputContainingFilenames() {
 189         long pid = ProcessHandle.current().pid();
 190 
 191         String[] command = lsofCommand().orElseThrow(() -> new RuntimeException("lsof like command not found"));
 192         System.out.println("using command: " + command[0] + " " + command[1]);
 193         return run(command[0], command[1], "" + pid).collect(toList());
 194     }
 195 
 196     static boolean findOpenLogFile(Collection<String> fileNames) {
 197         return fileNames.stream()
 198             .filter(fileName -> fileName.contains(LOG_SUFFIX))
 199             .findAny()
 200             .isPresent();
 201     }
 202 
 203     static void windows(File f, long parentPid) throws InterruptedException {
 204         System.out.println("waiting for pid: " + parentPid);
 205         ProcessHandle.of(parentPid).ifPresent(handle -> handle.onExit().join());
 206         System.out.println("trying to rename file to the same name: " + f);
 207         System.out.println(f.renameTo(f) ? RETAINS_FD : LEAKS_FD); // this parts communicates a closed file descriptor by printing "VM RESULT => RETAINS FD"
 208     }
 209 }
 210