001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.util; 019 020import java.io.BufferedReader; 021import java.io.File; 022import java.io.FileReader; 023import java.io.FileWriter; 024import java.io.IOException; 025import java.io.InputStreamReader; 026import java.lang.management.ManagementFactory; 027import java.lang.management.RuntimeMXBean; 028import java.lang.reflect.Field; 029import java.lang.reflect.Method; 030import java.util.logging.Level; 031import java.util.logging.Logger; 032 033/** 034 * Utility class to manage a PID file in a platform-independent manner. The 035 * class only offers two few public methods to create a PID file for the current 036 * Java process, and to check whether the process indicated by a PID file is 037 * still running. 038 * 039 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 040*/ 041public class PidFileUtil { 042 private static final Logger logger = Logger.getLogger(PidFileUtil.class.getSimpleName()); 043 044 /** 045 * Determines the PID for the current Java process. 046 * 047 * <p>This is a non-trivial action, since Java does not offer an easy API. This method tries to 048 * determine the PID using two different methods {@link #getProcessPidImpl1()} and 049 * {@link #getProcessPidImpl2()} (if the first one fails) and returns the PID it it succeeds. 050 * 051 * @return The Java process PID, or -1 if the PID cannot be determined 052 */ 053 public static int getProcessPid() { 054 try { 055 return getProcessPidImpl1(); 056 } 057 catch (Exception e) { 058 logger.log(Level.WARNING, "Retrieving Java Process PID failed with first method, trying second ..."); 059 060 try { 061 return getProcessPidImpl2(); 062 } 063 catch (Exception e1) { 064 return -1; 065 } 066 } 067 } 068 069 /** 070 * Determines the process identifier (PID) for the currently active Java process and writes this 071 * PID to the given file. 072 * 073 * @see #getProcessPid() 074 */ 075 public static void createPidFile(File pidFile) throws IOException { 076 pidFile.delete(); 077 078 try (FileWriter pidFileWriter = new FileWriter(pidFile)) { 079 String pidStr = "" + getProcessPid(); 080 081 logger.log(Level.INFO, "Writing PID file (for PID " + pidStr + ") to " + pidFile + " ..."); 082 083 pidFileWriter.write(pidStr); 084 pidFileWriter.close(); 085 } 086 087 pidFile.deleteOnExit(); 088 } 089 090 /** 091 * Determines whether a process is running, based on the given PID file. The method 092 * reads the PID file and then calls {@link #isProcessRunning(int)}. If the PID file 093 * does not exist, it returns <code>false</code>. 094 */ 095 public static boolean isProcessRunning(File pidFile) { 096 if (pidFile.exists()) { 097 try (BufferedReader pidFileReader = new BufferedReader(new FileReader(pidFile))) { 098 int pid = Integer.parseInt(pidFileReader.readLine()); 099 return isProcessRunning(pid); 100 } 101 catch (Exception e) { 102 logger.log(Level.WARNING, "Cannot read pidfile from " + pidFile + ". Assuming process not running."); 103 return false; 104 } 105 } 106 else { 107 return false; 108 } 109 } 110 111 /** 112 * Determines whether a process with the given PID is running. Depending on the 113 * underlying OS, this method either calls {@link #isProcessRunningUnixLike(int)} 114 * or {@link #isProcessRunningWindows(int)}. 115 */ 116 private static boolean isProcessRunning(int pid) { 117 if (EnvironmentUtil.isUnixLikeOperatingSystem()) { 118 return isProcessRunningUnixLike(pid); 119 } 120 else if (EnvironmentUtil.isWindows()) { 121 return isProcessRunningWindows(pid); 122 } 123 124 return false; 125 } 126 127 /** 128 * Uses the {@link RuntimeMXBean}'s name to determine the PID. On Linux, this name 129 * typically has a value like <code>12345@localhost</code> where 12345 is the PID. 130 * However, this is not guaranteed for every VM, so this is only one of two implementations. 131 * 132 * @see http://stackoverflow.com/a/35885/1440785 133 */ 134 private static int getProcessPidImpl1() throws Exception { 135 String pidStr = ManagementFactory.getRuntimeMXBean().getName(); 136 137 if (pidStr.contains("@")) { 138 int processPid = Integer.parseInt(pidStr.split("@")[0]); 139 140 logger.log(Level.INFO, "Java Process PID is " + processPid); 141 return processPid; 142 } 143 else { 144 throw new Exception("Cannot find pid from string: " + pidStr); 145 } 146 } 147 148 /** 149 * Uses the private method <code>VMManagement.getProcessId()</code> of Sun's <code>sun.management.VMManagement</code> 150 * class to determine the PID (using reflection to make the relevant fields visible). 151 * 152 * @see http://stackoverflow.com/a/12066696/1440785 153 */ 154 private static int getProcessPidImpl2() throws Exception { 155 RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); 156 Field jvmField = runtimeMXBean.getClass().getDeclaredField("jvm"); 157 jvmField.setAccessible(true); 158 159 // The returned object is of the type 'sun.management.VMManagement', but since we 160 // don't need the exact type here and we don't want to reference it in the 161 // imports, we'll just hope it's there. 162 163 Object vmManagement = jvmField.get(runtimeMXBean); 164 Method getProcessIdMethod = vmManagement.getClass().getDeclaredMethod("getProcessId"); 165 166 getProcessIdMethod.setAccessible(true); 167 int processPid = (Integer) getProcessIdMethod.invoke(vmManagement); 168 169 logger.log(Level.INFO, "Java Process PID is " + processPid); 170 return processPid; 171 } 172 173 /** 174 * Determines whether a process with the given PID is running using the POSIX 175 * <code>ps -p $pid</code> command. 176 * 177 * @param pid Process ID (PID) to check 178 * @return True if process is running, false otherwise 179 */ 180 private static boolean isProcessRunningUnixLike(int pid) { 181 try { 182 Runtime runtime = Runtime.getRuntime(); 183 Process process = runtime.exec(new String[] { "/bin/ps", "-p", ""+pid }); 184 185 int killProcessExitCode = process.waitFor(); 186 boolean processRunning = killProcessExitCode == 0; 187 188 logger.log(Level.INFO, "isProcessRunningUnixLike(" + pid + ") returned " + killProcessExitCode + ", process running = " + processRunning); 189 return processRunning; 190 } 191 catch (Exception e) { 192 logger.log(Level.SEVERE, "Cannot retrieve status of PID " + pid + "; assuming process not running."); 193 return false; 194 } 195 } 196 197 /** 198 * Determines whether a process with the given PID is running using the Windows 199 * <code>tasklist</code> command. 200 * 201 * @see http://stackoverflow.com/questions/2533984/java-checking-if-any-process-id-is-currently-running-on-windows 202 */ 203 private static boolean isProcessRunningWindows(int pid) { 204 try { 205 Process tasklistProcess = Runtime.getRuntime().exec(new String[] { "cmd", "/c", "tasklist /FI \"PID eq " + pid + "\"" }); 206 BufferedReader tasklistOutputReader = new BufferedReader(new InputStreamReader(tasklistProcess.getInputStream())); 207 208 String line = null; 209 boolean processRunning = false; 210 211 while ((line = tasklistOutputReader.readLine()) != null) { 212 if (line.contains(" " + pid + " ")) { 213 processRunning = true; 214 break; 215 } 216 } 217 218 logger.log(Level.INFO, "isProcessRunningWindows(" + pid + ") returned " + line + ", process running = " + processRunning); 219 return processRunning; 220 } 221 catch (Exception ex) { 222 logger.log(Level.SEVERE, "Cannot retrieve status of PID " + pid + "; assuming process not running."); 223 return false; 224 } 225 } 226}