All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gemstone.gemfire.internal.OSProcess Maven / Gradle / Ivy

There is a newer version: 2.0-BETA
Show newest version
/*
 * Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You
 * may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License. See accompanying
 * LICENSE file.
 */
package com.gemstone.gemfire.internal;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPOutputStream;

import com.gemstone.gemfire.LogWriter;
import com.gemstone.gemfire.SystemFailure;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.cache.GemFireCacheImpl;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.io.TeePrintStream;
import com.gemstone.gemfire.internal.shared.NativeCalls;
import com.gemstone.gemfire.internal.shared.NativeCalls.NativeCallsGeneric;

/**
 * Used to interact with operating system processes.
 * Use exec to create a new process by executing a command.
 * Use kill to kill a process.
 *
 * @author darrel
 *
 */
public class OSProcess {
    public static final String DISABLE_OUTPUT_REDIRECTION_PROP = "gemfire.OSProcess.DISABLE_OUTPUT_REDIRECTION";
  
    private static final boolean DISABLE_OUTPUT_REDIRECTION = Boolean
        .getBoolean(DISABLE_OUTPUT_REDIRECTION_PROP);

    static final boolean pureMode = PureJavaMode.isPure();

    static {
      if (!pureMode) {
        registerSigQuitHandler();
      }
    }

    /**
     * A {@link NativeCalls} implementation using JNI calls in {@link OSProcess}
     * where possible. At some point all the functionality of OSProcess should
     * be moved into NativeCallsJNAImpl which will be much more maintainable.
     */
    public static final class NativeOSCalls extends NativeCallsGeneric {

      /**
       * {@inheritDoc}
       */
      @Override
      public int getProcessId() {
        return getId2();
      }

      /**
       * {@inheritDoc}
       */
      @Override
      public boolean isProcessActive(int processId)
          throws UnsupportedOperationException {
        if (processId != 0) {
          try {
            return super.isProcessActive(processId);
          } catch (UnsupportedOperationException e) {
            return exists2(processId);
          }
        }
        else {
          return true;
        }
      }

      /**
       * {@inheritDoc}
       */
      @Override
      public boolean killProcess(int processId)
          throws UnsupportedOperationException {
        if (pureMode) {
          throw new UnsupportedOperationException(
              LocalizedStrings.OSProcess_KILL_NOT_ALLOWED_IN_PURE_JAVA_MODE
                  .toLocalizedString());
        }
        else {
          return kill2(processId);
        }
      }
    }

    /**
     * Starts a background command writing its stdout and stderr to
     * the specified log file.
     *
     * @param cmdarray An array of strings that specify the command to run.
     * The first element must be the executable.
     * Each additional command line argument should have its own entry
     *  in the array.
     * @param workdir the current directory of the created process
     * @param logfile the file the created process will write
     * stdout and stderr to.
     * @param inheritLogfile can be set to false if the child process
     *  is willing to create its own log file. Setting to false can help
     *  on Windows because it keeps the child process from inheriting
     *  handles from the parent.
     * @return the process id of the created process; -1 on failure
     * @exception IOException if a child process could not be created.
     */
    private static native int bgexecInternal(String[] cmdarray,
					     String workdir,
					     String logfile,
                                             boolean inheritLogfile)
	throws IOException;

    /**
     * Starts execution of the specified command and arguments in a separate
     * detached process in the specified
     * working directory writing output to the specified log file.
     * 

* If there is a security manager, its checkExec method * is called with the first component of the array * cmdarray as its argument. This may result in a security * exception. *

* Given an array of strings cmdarray, representing the * tokens of a command line, * this method creates a new process in which to execute * the specified command. * * @param cmdarray array containing the command to call and its arguments. * @param workdir the current directory of the created process; null * causes working directory to default to the current directory. * @param logfile the file the created process will write * stdout and stderr to; null causes a default log file name * to be used. * @param inheritLogfile can be set to false if the child process * is willing to create its own log file. Setting to false can help * on Windows because it keeps the child process from inheriting * handles from the parent. * @param env any extra environment variables as key,value map; * these will be in addition to those inherited from the * parent process and will overwrite same keys * @return the process id of the created process; -1 on failure * @exception SecurityException if the current thread cannot create a * subprocess. * @see java.lang.SecurityException * @see java.lang.SecurityManager#checkExec(java.lang.String) */ public static int bgexec(String cmdarray[], File workdir, File logfile, boolean inheritLogfile, Map env) throws IOException { String commandShell = System.getProperty("gemfire.commandShell", "bash"); if (cmdarray.length == 0) { throw new java.lang.IndexOutOfBoundsException(); } boolean isWindows = false; String os = System.getProperty("os.name"); if (os != null) { if (os.indexOf("Windows") != -1) { isWindows = true; } } for (int i = 0; i < cmdarray.length; i++) { if (cmdarray[i] == null) { throw new NullPointerException(); } if (isWindows) { if (i == 0) { // do the following before quotes get added. File cmd = new File(cmdarray[0]); if (!cmd.exists()) { cmd = new File(cmdarray[0] + ".exe"); if (cmd.exists()) { cmdarray[0] = cmd.getPath(); } } } String s = cmdarray[i]; if (i != 0) { if (s.length() == 0) { cmdarray[i] = "\"\""; // fix for bug 22207 } else if ((s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0)) { String unquotedS = s; if (s.indexOf('\"') != -1) { // Note that Windows provides no way to embed a double // quote in a double quoted string so need to remove // any internal quotes and let the outer quotes // preserve the whitespace. StringBuffer b = new StringBuffer(s); int quoteIdx = s.lastIndexOf('\"'); while (quoteIdx != -1) { b.deleteCharAt(quoteIdx); quoteIdx = s.lastIndexOf('\"', quoteIdx-1); } unquotedS = b.toString(); } // It has whitespace and its not quoted cmdarray[i] = '"' + unquotedS + '"'; } } } } File cmd = new File(cmdarray[0]); if (!cmd.exists()) { throw new IOException(LocalizedStrings.OSProcess_THE_EXECUTABLE_0_DOES_NOT_EXIST.toLocalizedString(cmd.getPath())); } SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkExec(cmdarray[0]); } if (workdir != null && !workdir.isDirectory()) { String curDir = new File("").getAbsolutePath(); System.out.println( LocalizedStrings.OSProcess_WARNING_0_IS_NOT_A_DIRECTORY_DEFAULTING_TO_CURRENT_DIRECTORY_1.toLocalizedString(new Object[] {workdir, curDir})); workdir = null; } if (workdir == null) { workdir = new File("").getAbsoluteFile(); } if (logfile == null) { logfile = File.createTempFile("bgexec", ".log", workdir); } if (!logfile.isAbsolute()) { // put it in the working directory logfile = new File(workdir, logfile.getPath()); } // fix for bug 24575 if (logfile.exists()) { // it already exists so make sure its a file and can be written if (!logfile.isFile()) { throw new IOException(LocalizedStrings.OSProcess_THE_LOG_FILE_0_WAS_NOT_A_NORMAL_FILE.toLocalizedString(logfile.getPath())); } if (!logfile.canWrite()) { throw new IOException(LocalizedStrings.OSProcess_NEED_WRITE_ACCESS_FOR_THE_LOG_FILE_0.toLocalizedString(logfile.getPath())); } } else { try { logfile.createNewFile(); } catch (IOException io) { throw new IOException(LocalizedStrings.OSProcess_COULD_NOT_CREATE_LOG_FILE_0_BECAUSE_1.toLocalizedString(new Object[] {logfile.getPath(), io.getMessage()})); } } String trace = System.getProperty("com.gemstone.gemfire.internal.OSProcess.trace"); if (trace != null && trace.length() > 0) { for (int i=0; i < cmdarray.length; i++) { System.out.println("cmdarray[" + i + "] = " + cmdarray[i]); } System.out.println("workdir=" + workdir.getPath()); System.out.println("logfile=" + logfile.getPath()); } int result = 0; if (pureMode || (env != null && env.size() > 0)) { final StringBuilder sb = new StringBuilder(); final ArrayList cmdVec = new ArrayList(); //Add shell code to spawn a process silently if(isWindows) { cmdVec.add("cmd.exe"); cmdVec.add("/c"); sb.append("start /b \"\" "); } else { //to address Adobe issue with customers that don't have bash shell installed if(commandShell.equals("bash")){ cmdVec.add("bash"); cmdVec.add("--norc"); cmdVec.add("-c"); }else{ cmdVec.add(commandShell); } } //Add the actual command for (int i=0; i < cmdarray.length; i++) { if (i != 0) sb.append(" "); if(cmdarray[i].length() != 0 && cmdarray[i].charAt(0) == '\"') { //The token has already been quoted, see bug 40835 sb.append(cmdarray[i]); } else { sb.append("\""); sb.append(cmdarray[i]); sb.append("\""); } } //Add the IO redirction code, this prevents hangs and IO blocking sb.append(" >> "); if (isWindows) { sb.append("\"").append(logfile.getPath()).append("\""); } else { sb.append(logfile.getPath()); } sb.append(" 2>&1"); if(isWindows) { sb.append(" 0) { for (int i=0; i < cmdStrings.length; i++) { System.out.println("cmdStrings[" + i + "] = " + cmdStrings[i]); } System.out.println("workdir=" + workdir.getPath()); System.out.println("logfile=" + logfile.getPath()); } final ProcessBuilder procBuilder = new ProcessBuilder(cmdStrings); if (env != null && env.size() > 0) { // adjust the environment variables inheriting from parent procBuilder.environment().putAll(env); } procBuilder.directory(workdir); final Process process = procBuilder.start(); try { process.getInputStream().close(); } catch(IOException ignore){} try { process.getOutputStream().close(); } catch(IOException ignore){} try { process.getErrorStream().close(); } catch(IOException ignore){} try { // short count = 1000; boolean processIsStillRunning = true; while(processIsStillRunning) { Thread.sleep(10); try { process.exitValue(); processIsStillRunning = false; } catch(IllegalThreadStateException itse) { // Ignore this, we are polling the exitStatus // instead of using the blocking Process#waitFor() } } } catch(InterruptedException ie) { Thread.currentThread().interrupt(); } } else { result = bgexecInternal(cmdarray, workdir.getPath(), logfile.getPath(), inheritLogfile); if (result != -1) { if (pids != null) { pids.add(Integer.valueOf(result)); if (trace != null && trace.length() > 0) { System.out.println("bgexec child pid is: " + result); } } } } return result; //Always 0 for pureJava } /** * Checks to make sure that we are operating on a valid process id. * Sending signals to processes with pid 0 or -1 can * have unintended consequences. * * @throws IllegalArgumentException * If pid is not positive * * @since 4.0 */ private static void checkPid(int pid) { if (pid <= 0) { throw new IllegalArgumentException(LocalizedStrings.OSProcess_SHOULD_NOT_SEND_A_SIGNAL_TO_PID_0.toLocalizedString(Integer.valueOf(pid))); } } /** * Ask a process to shut itself down. * The process may catch and ignore this shutdown request. * @param pid the id of the process to shutdown * @return true if the request was sent to the process; * false if the process does not exist or can not be asked to shutdown. */ static public boolean shutdown(int pid) { if (pureMode) { throw new RuntimeException(LocalizedStrings.OSProcess_SHUTDOWN_NOT_ALLOWED_IN_PURE_JAVA_MODE.toLocalizedString()); } else { checkPid(pid); return _shutdown(pid); } } static private native boolean _shutdown(int pid); /** * Terminate a process without warning and without a chance of an * orderly shutdown. This method should only be used as a last resort. * The {@link #shutdown(int)} method should be used in most cases. * @param pid the id of the process to kill * @return true if the process was killed; * false if it does not exist or can not be killed. */ static public boolean kill(int pid) { return NativeCalls.getInstance().killProcess(pid); } static boolean kill2(int pid) { if (pureMode) { throw new RuntimeException(LocalizedStrings.OSProcess_KILL_NOT_ALLOWED_IN_PURE_JAVA_MODE.toLocalizedString()); } else { checkPid(pid); return _kill(pid); } } static private native boolean _kill(int pid); /** * Tells a process to print its stacks to its standard output * @param pid the id of the process that will print its stacks, or zero for the current process * @return true if the process was told; * false if it does not exist or can not be told. */ static public boolean printStacks(int pid) { return printStacks(pid, null, false); } /** * Tells a process to print its stacks to its standard output or the given log writer * @param pid the id of the process that will print its stacks, or zero for the current process * @param logger the log writer to use if native code is not available * @param useNative if true we attempt to use native code, which goes to stdout * @return true if the process was told; * false if it does not exist or can not be told. */ static public boolean printStacks(int pid, LogWriter logger, boolean useNative) { if (pureMode || !useNative) { if (pid > 0 && pid != myPid[0]) { return false; } LogWriter log = logger; if (log == null) { log = new LocalLogWriter(LogWriterImpl.ALL_LEVEL); } CharArrayWriter cw = new CharArrayWriter(50000); PrintWriter sb = new PrintWriter(cw, true); sb.append("\n******** full thread dump ********\n"); ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long[] threadIds = bean.getAllThreadIds(); ThreadInfo[] infos = bean.getThreadInfo(threadIds, true, true); long thisThread = Thread.currentThread().getId(); for (int i=0; i 0) { pw.append("\n\tNumber of locked synchronizers = " + locks.length); pw.append('\n'); for (LockInfo li : locks) { pw.append("\t- " + li); pw.append('\n'); } } pw.append('\n'); } /** * Find out if a process exists. * * @param pid * the id of the process to check for * @return true if the process exists; false if it does not. */ static public boolean exists(int pid) { return NativeCalls.getInstance().isProcessActive(pid); } /** * Find out if a process exists. * @param pid the id of the process to check for * @return true if the process exists; false if it does not. */ static boolean exists2(int pid) { if (pureMode) { throw new RuntimeException(LocalizedStrings.OSProcess_EXISTS_NOT_ALLOWED_IN_PURE_JAVA_MODE.toLocalizedString()); } checkPid(pid); if (reapPid(pid)) { try { pids.remove(Integer.valueOf(pid)); } catch (Exception ignore) {} String trace = System.getProperty("com.gemstone.gemfire.internal.OSProcess.trace"); if (trace != null && trace.length() > 0) { System.out.println("reaped pid: " + pid); } } return nativeExists(pid); } private static native boolean nativeExists(int pid); // Private stuff /** * Waits for a child process to die and reaps it. */ static private native void waitForPid(int pid); /** * Waits until the identified process exits. If the process does * not exist then returns immediately. */ static public void waitForPidToExit(int pid) { if (pureMode) { throw new RuntimeException(LocalizedStrings.OSProcess_WAITFORPIDTOEXIT_NOT_ALLOWED_IN_PURE_JAVA_MODE.toLocalizedString()); } checkPid(pid); waitForPid(pid); } /** * Sets the current directory of this process. * @return true if current directory was set; false if not. */ static public boolean setCurrentDirectory(File curDir) { if (pureMode) { throw new RuntimeException(LocalizedStrings.OSProcess_SETCURRENTDIRECTORY_NOT_ALLOWED_IN_PURE_JAVA_MODE.toLocalizedString()); } return jniSetCurDir(curDir.getAbsolutePath()); } /** * Returns true on success. Returns false and current directory * is unchanged on failure. */ private static native boolean jniSetCurDir(String dir); /** * Reaps a child process if it has died. * Does not wait for the child. * @param pid the id of the process to reap * @return true if it was reaped or lost (someone else reaped it); * false if the child still exists. * HACK: If pid is -1 then returns true if this platform needs reaping. */ protected static native boolean reapPid(int pid); private static Thread reaperThread; protected static Set pids = null; // myPid caches result of getProcessId . To provide a stable processId // on Linux, where processId may differ per thread, we cache the // processId of the reaper thread . static final int[] myPid = new int[1]; // cache of my processId static boolean reaperStarted = false; // true if cache is valid /** On Linux, getProcessId returns the processId of the calling thread */ static native int getProcessId(); static { if (pureMode) { //just initialize the pid cache synchronized (myPid) { int pid = 0; // Windows checks have been disabled as the ManagementFactory hack // to find the PID has been seen to work on Windows 7. Add checks // for more specific versions of Windows if this fails on them // if(! System.getProperty("os.name", "").startsWith("Windows")) { String name = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); int idx = name.indexOf('@'); try { pid = Integer.parseInt(name.substring(0,idx)); } catch(NumberFormatException nfe) { //something changed in the RuntimeMXBean name } // } myPid[0] = pid; reaperStarted = true; } } else { if (reapPid(-1)) { pids = Collections.synchronizedSet(new HashSet()); ThreadGroup group = LogWriterImpl.createThreadGroup(LocalizedStrings.OSProcess_REAPER_THREAD.toLocalizedString(), (LogWriterI18n)null); reaperThread = new Thread(group, new Runnable() { public void run() { synchronized (myPid) { myPid[0] = getProcessId(); reaperStarted = true; } String trace = System.getProperty("com.gemstone.gemfire.internal.OSProcess.trace"); int secondsToSleep = (1000 * 60) * 1; // one minute if (trace != null && trace.length() > 0) { secondsToSleep = 1000; // every second } // reap all the pids we have every once in a while while (true) { SystemFailure.checkFailure(); try { Iterator it = pids.iterator(); while (it.hasNext()) { Object o = it.next(); int pid = ((Integer)o).intValue(); if (reapPid(pid)) { try { it.remove(); if (trace != null && trace.length() > 0) { System.out.println("reaped pid: " + pid); } } catch (Exception e) { // make sure and remove it since it was // reaped. pids.remove(o); if (trace != null && trace.length() > 0) { System.out.println("reaped pid: " + pid); } throw e; } } } Thread.sleep(secondsToSleep); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } catch (Exception e) { // e.printStackTrace(); // DEBUG // ignore } } }}, "osprocess reaper"); reaperThread.setDaemon(true); reaperThread.start(); } else { // platform does not need a reaper thread, // just initialize the pid cache synchronized (myPid) { myPid[0] = getProcessId(); reaperStarted = true; } } } } /** * Get the vm's process id. This uses JNA to get the process Id else falls * back to {@link OSProcess#getId2()}. * * @return the vm's process id. */ public static int getId() { return NativeCalls.getInstance().getProcessId(); } /** * Get the vm's process id. On Linux, this returns the processId * of the reaper thread. If we are in {@linkplain * PureJavaMode#isPure pure Java mode}, then 0 is * returned. * * @return the vm's process id. */ static int getId2() { boolean done = false; int result = -1; for (;;) { synchronized (myPid) { done = reaperStarted; result = myPid[0]; } if (done) break; // wait for reaper thread to initialize myPid try { Thread.sleep(100); } catch (InterruptedException ignore) { Thread.currentThread().interrupt(); } } return result; } public static PrintStream redirectOutput(File newOutput) throws IOException { FileOutputStream newFileStream = null; try { newFileStream = new FileOutputStream(newOutput, true); } catch (FileNotFoundException e) { throw new IOException("File not found: " + newOutput, e); } final PrintStream newPrintStream = new PrintStream(new BufferedOutputStream(newFileStream, 128), true); if (!DISABLE_OUTPUT_REDIRECTION) { System.setOut(newPrintStream); if (System.err instanceof TeePrintStream) { ((TeePrintStream) System.err).getTeeOutputStream().setBranchOutputStream(new BufferedOutputStream(newFileStream, 128)); } else { System.setErr(newPrintStream); } if (!pureMode) { redirectCOutput(newOutput.getPath()); } } return newPrintStream; } private static native void redirectCOutput(String file); /** * Registers a signal handler for SIGQUIT on UNIX platforms. */ private static native void registerSigQuitHandler(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy