1 /* 2 * Copyright (c) 2019, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.tools; 27 28 import java.io.IOException; 29 import java.io.UncheckedIOException; 30 import java.nio.file.Files; 31 import java.nio.file.Paths; 32 import java.util.jar.Attributes; 33 import java.util.jar.JarFile; 34 import java.util.jar.Manifest; 35 import java.util.stream.Stream; 36 37 /** 38 * A helper class that retrieves the main class name for 39 * a running Java process using the proc filesystem (procfs) 40 */ 41 public class ProcessHelper implements sun.tools.common.ProcessHelper { 42 43 44 private static final String CMD_PREFIX = "cmd:"; 45 private static final ProcessHelper INSTANCE = new ProcessHelper(); 46 47 public static ProcessHelper getInstance() { 48 return INSTANCE; 49 } 50 51 /** 52 * Gets the main class name for the given Java process by parsing the 53 * process command line. 54 * @param pid - process ID (pid) 55 * @return main class name or null if the process no longer exists or 56 * was started with a native launcher (e.g. jcmd etc) 57 */ 58 59 public String getMainClass(String pid) { 60 String cmdLine = getCommandLine(pid); 61 if (cmdLine == null) { 62 return null; 63 } 64 if (cmdLine.startsWith(CMD_PREFIX)) { 65 cmdLine = cmdLine.substring(CMD_PREFIX.length()); 66 } 67 String[] parts = cmdLine.split(" "); 68 String mainClass = null; 69 70 if(parts.length == 0) { 71 return null; 72 } 73 74 // Check the executable 75 String[] executablePath = parts[0].split("/"); 76 if (executablePath.length > 0) { 77 String binaryName = executablePath[executablePath.length - 1]; 78 if (!"java".equals(binaryName)) { 79 // Skip the process if it is not started with java launcher 80 return null; 81 } 82 } 83 84 // If -jar option is used then read the main class name from the manifest file. 85 // Otherwise, the main class name is either specified in -m or --module options or it 86 // is the first part that is not a Java option (doesn't start with '-' and is not a 87 // classpath or a module path). 88 89 for (int i = 1; i < parts.length && mainClass == null; i++) { 90 if (i < parts.length - 1) { 91 // Check if the module is executed with explicitly specified main class 92 if ((parts[i].equals("-m") || parts[i].equals("--module"))) { 93 return getMainClassFromModuleArg(parts[i + 1]); 94 } 95 // Check if the main class needs to be read from the manifest.mf in a JAR file 96 if (parts[i].equals("-jar")) { 97 return getMainClassFromJar(parts[i + 1], pid); 98 } 99 } 100 // If this is a classpath or a module path option then skip the next part 101 // (the classpath or the module path itself) 102 if (parts[i].equals("-cp") || parts[i].equals("-classpath") || parts[i].equals("--class-path") || 103 parts[i].equals("-p") || parts[i].equals("--module-path")) { 104 i++; 105 continue; 106 } 107 // Skip all other Java options 108 if (parts[i].startsWith("-")) { 109 continue; 110 } 111 mainClass = parts[i]; 112 } 113 return mainClass; 114 115 } 116 117 private String getMainClassFromModuleArg(String moduleArg) { 118 int pos = moduleArg.lastIndexOf("/"); 119 return (pos > 0 && pos < moduleArg.length()-1) ? moduleArg.substring(pos + 1) : null; 120 } 121 122 private String getMainClassFromJar(String jar, String pid) { 123 if (!jar.startsWith("/")) { 124 String cwd = getCurrentWorkingDir(pid); 125 if (cwd != null) { 126 jar = cwd + "/" + jar; 127 } 128 } 129 try (JarFile jarFile = new JarFile(jar)) { 130 Manifest mf = jarFile.getManifest(); 131 if (mf != null) { 132 Attributes mainAttributes = mf.getMainAttributes(); 133 return mainAttributes.getValue("Main-Class"); 134 } 135 } catch (IOException e) { 136 return null; 137 } 138 return null; 139 } 140 141 private static String getCurrentWorkingDir(String pid) { 142 return ("/proc/" + pid + "/cwd"); 143 } 144 145 private static String getCommandLine(String pid) { 146 try (Stream<String> lines = 147 Files.lines(Paths.get("/proc/" + pid + "/cmdline"))) { 148 return lines.map(x -> x.replaceAll("\0", " ")).findFirst().orElse(null); 149 } catch (IOException | UncheckedIOException e) { 150 return null; 151 } 152 } 153 } 154 155