/*
* Syncany, www.syncany.org
* Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.syncany.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Utility class to manage a PID file in a platform-independent manner. The
* class only offers two few public methods to create a PID file for the current
* Java process, and to check whether the process indicated by a PID file is
* still running.
*
* @author Philipp C. Heckel <philipp.heckel@gmail.com>
*/
public class PidFileUtil {
private static final Logger logger = Logger.getLogger(PidFileUtil.class.getSimpleName());
/**
* Determines the PID for the current Java process.
*
* <p>This is a non-trivial action, since Java does not offer an easy API. This method tries to
* determine the PID using two different methods {@link #getProcessPidImpl1()} and
* {@link #getProcessPidImpl2()} (if the first one fails) and returns the PID it it succeeds.
*
* @return The Java process PID, or -1 if the PID cannot be determined
*/
public static int getProcessPid() {
try {
return getProcessPidImpl1();
}
catch (Exception e) {
logger.log(Level.WARNING, "Retrieving Java Process PID failed with first method, trying second ...");
try {
return getProcessPidImpl2();
}
catch (Exception e1) {
return -1;
}
}
}
/**
* Determines the process identifier (PID) for the currently active Java process and writes this
* PID to the given file.
*
* @see #getProcessPid()
*/
public static void createPidFile(File pidFile) throws IOException {
pidFile.delete();
try (FileWriter pidFileWriter = new FileWriter(pidFile)) {
String pidStr = "" + getProcessPid();
logger.log(Level.INFO, "Writing PID file (for PID " + pidStr + ") to " + pidFile + " ...");
pidFileWriter.write(pidStr);
pidFileWriter.close();
}
pidFile.deleteOnExit();
}
/**
* Determines whether a process is running, based on the given PID file. The method
* reads the PID file and then calls {@link #isProcessRunning(int)}. If the PID file
* does not exist, it returns <tt>false</tt>.
*/
public static boolean isProcessRunning(File pidFile) {
if (pidFile.exists()) {
try (BufferedReader pidFileReader = new BufferedReader(new FileReader(pidFile))) {
int pid = Integer.parseInt(pidFileReader.readLine());
return isProcessRunning(pid);
}
catch (Exception e) {
logger.log(Level.WARNING, "Cannot read pidfile from " + pidFile + ". Assuming process not running.");
return false;
}
}
else {
return false;
}
}
/**
* Determines whether a process with the given PID is running. Depending on the
* underlying OS, this method either calls {@link #isProcessRunningUnixLike(int)}
* or {@link #isProcessRunningWindows(int)}.
*/
private static boolean isProcessRunning(int pid) {
if (EnvironmentUtil.isUnixLikeOperatingSystem()) {
return isProcessRunningUnixLike(pid);
}
else if (EnvironmentUtil.isWindows()) {
return isProcessRunningWindows(pid);
}
return false;
}
/**
* Uses the {@link RuntimeMXBean}'s name to determine the PID. On Linux, this name
* typically has a value like <tt>12345@localhost</tt> where 12345 is the PID.
* However, this is not guaranteed for every VM, so this is only one of two implementations.
*
* @see http://stackoverflow.com/a/35885/1440785
*/
private static int getProcessPidImpl1() throws Exception {
String pidStr = ManagementFactory.getRuntimeMXBean().getName();
if (pidStr.contains("@")) {
int processPid = Integer.parseInt(pidStr.split("@")[0]);
logger.log(Level.INFO, "Java Process PID is " + processPid);
return processPid;
}
else {
throw new Exception("Cannot find pid from string: " + pidStr);
}
}
/**
* Uses the private method <tt>VMManagement.getProcessId()</tt> of Sun's <tt>sun.management.VMManagement</tt>
* class to determine the PID (using reflection to make the relevant fields visible).
*
* @see http://stackoverflow.com/a/12066696/1440785
*/
private static int getProcessPidImpl2() throws Exception {
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
Field jvmField = runtimeMXBean.getClass().getDeclaredField("jvm");
jvmField.setAccessible(true);
// The returned object is of the type 'sun.management.VMManagement', but since we
// don't need the exact type here and we don't want to reference it in the
// imports, we'll just hope it's there.
Object vmManagement = jvmField.get(runtimeMXBean);
Method getProcessIdMethod = vmManagement.getClass().getDeclaredMethod("getProcessId");
getProcessIdMethod.setAccessible(true);
int processPid = (Integer) getProcessIdMethod.invoke(vmManagement);
logger.log(Level.INFO, "Java Process PID is " + processPid);
return processPid;
}
/**
* Determines whether a process with the given PID is running using the POSIX
* <tt>ps -p $pid</tt> command.
*
* @param pid Process ID (PID) to check
* @return True if process is running, false otherwise
*/
private static boolean isProcessRunningUnixLike(int pid) {
try {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(new String[] { "/bin/ps", "-p", ""+pid });
int killProcessExitCode = process.waitFor();
boolean processRunning = killProcessExitCode == 0;
logger.log(Level.INFO, "isProcessRunningUnixLike(" + pid + ") returned " + killProcessExitCode + ", process running = " + processRunning);
return processRunning;
}
catch (Exception e) {
logger.log(Level.SEVERE, "Cannot retrieve status of PID " + pid + "; assuming process not running.");
return false;
}
}
/**
* Determines whether a process with the given PID is running using the Windows
* <tt>tasklist</tt> command.
*
* @see http://stackoverflow.com/questions/2533984/java-checking-if-any-process-id-is-currently-running-on-windows
*/
private static boolean isProcessRunningWindows(int pid) {
try {
Process tasklistProcess = Runtime.getRuntime().exec(new String[] { "cmd", "/c", "tasklist /FI \"PID eq " + pid + "\"" });
BufferedReader tasklistOutputReader = new BufferedReader(new InputStreamReader(tasklistProcess.getInputStream()));
String line = null;
boolean processRunning = false;
while ((line = tasklistOutputReader.readLine()) != null) {
if (line.contains(" " + pid + " ")) {
processRunning = true;
break;
}
}
logger.log(Level.INFO, "isProcessRunningWindows(" + pid + ") returned " + line + ", process running = " + processRunning);
return processRunning;
}
catch (Exception ex) {
logger.log(Level.SEVERE, "Cannot retrieve status of PID " + pid + "; assuming process not running.");
return false;
}
}
}