+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * 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.
+ */
package com.intellij.execution.process;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
-import com.sun.jna.Library;
-import com.sun.jna.Native;
-import com.sun.jna.Platform;
import org.jetbrains.annotations.NotNull;
-import java.io.*;
-import java.lang.reflect.Field;
-import java.util.*;
+import java.io.File;
+import java.io.OutputStream;
+import java.io.PrintWriter;
/**
- * This runner manages ctrl+break(ctrl+c) termination of process.
- *
* @author traff
*/
public class RunnerMediator {
- private static final Logger LOG = Logger.getInstance("#com.intellij.execution.process.RunnerMediator");
+ public static final Logger LOG = Logger.getInstance("#com.intellij.execution.process.RunnerMediator");
- private static final int SIGINT = 2;
- private static final int SIGKILL = 9;
- private static final String UID_KEY_NAME = "PROCESSUUID";
+ private static final char IAC = (char)5;
+ private static final char BRK = (char)3;
- private RunnerMediator() {
+ /**
+ * Creates default runner mediator
+ * @return
+ */
+ public static RunnerMediator getInstance() {
+ return new RunnerMediator();
}
- public static String getRunnerPath() {
- if (File.separatorChar == '\\') {
- return RunnerMediatorManager.getInstance().getRunnerPath();
+ /**
+ * Sends sequence of two chars(codes 5 and 3) to a process output stream
+ */
+ private static void sendCtrlBreakThroughStream(Process process) {
+ OutputStream os = process.getOutputStream();
+ PrintWriter pw = new PrintWriter(os);
+ try {
+ pw.print(IAC);
+ pw.print(BRK);
+ pw.flush();
}
- else {
- throw new IllegalStateException("There is no need of runner under unix based OS");
+ finally {
+ pw.close();
}
}
- public static void injectRunnerCommand(@NotNull GeneralCommandLine commandLine) {
- commandLine.getParametersList().addAt(0, commandLine.getExePath());
- commandLine.setExePath(getRunnerPath());
- }
-
- public static String injectUid(@NotNull GeneralCommandLine commandLine) {
- String uid = commandLine.getExePath() + ":" + UUID.randomUUID().toString();
- commandLine.getEnvParams().put(UID_KEY_NAME, uid);
- return uid;
- }
-
- public static ColoredProcessHandler createProcessWithStopInjections(@NotNull GeneralCommandLine commandLine)
- throws ExecutionException {
+ /**
+ * In case of windows creates process with runner mediator(runnerw.exe) injected to command line string, which adds a capability
+ * to terminate process tree gracefully with ctrl+break.
+ *
+ * Returns appropriate process handle, which in case of Unix is able to terminate whole process tree by sending sig_kill
+ *
+ */
+ public ProcessHandler createProcess(@NotNull GeneralCommandLine commandLine) throws ExecutionException {
if (isWindows()) {
injectRunnerCommand(commandLine);
}
- Process p = commandLine.createProcess();
+ Process process = commandLine.createProcess();
- return new CustomDestroyProcessHandler(p, commandLine);
+ return createProcessHandler(process, commandLine);
}
- public static boolean canSendSignals() {
- return SystemInfo.isLinux || SystemInfo.isMac;
- }
-
- public static boolean isWindows() {
- if (File.separatorChar == '\\') {
- return true;
- }
- return false;
- }
-
-
- public static void sendSigInt(Process process) {
- sendSignal(process, SIGINT);
- }
-
- public static void sendSigKill(Process process) {
- sendSignal(process, SIGKILL);
- }
-
- public static void sendSignal(Process process, int signal) {
- if (C_LIB == null) {
- throw new IllegalStateException("no CLIB");
- }
- int our_pid = C_LIB.getpid();
- int process_pid = getProcessPid(process);
-
- try {
- String[] psCmd = getCmd();
- Process p = Runtime.getRuntime().exec(psCmd);
-
- @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
- BufferedReader stdInput = new BufferedReader(new
- InputStreamReader(p.getInputStream()));
-
- BufferedReader stdError = new BufferedReader(new
- InputStreamReader(p.getErrorStream()));
- try {
- String s;
- stdInput.readLine(); //ps output header
- int foundPid = 0;
- while ((s = stdInput.readLine()) != null) {
- StringTokenizer st = new StringTokenizer(s, " ");
-
- int parent_pid = Integer.parseInt(st.nextToken());
- int pid = Integer.parseInt(st.nextToken());
-
- ProcessInfo.register(pid, parent_pid);
-
- if (pid == process_pid) {
- if (parent_pid == our_pid) {
- foundPid = pid;
- }
- else {
- throw new IllegalStateException("process is not our child");
- }
- }
- }
-
- if (foundPid != 0) {
- ProcessInfo.killProcTree(foundPid, signal);
- }
- else {
- throw new IllegalStateException("process not found: " + process_pid + ", idea pid =" + our_pid);
- }
-
- StringBuffer errorStr = new StringBuffer();
- while ((s = stdError.readLine()) != null) {
- errorStr.append(s).append("\n");
- }
- if (errorStr.length() > 0) {
- throw new IllegalStateException("error:" + errorStr.toString());
- }
- }
- finally {
- stdInput.close();
- stdError.close();
- }
- }
- catch (IOException e) {
- throw new IllegalStateException(e);
- }
+ /**
+ * Creates process handler for process able to be terminated with method RunnerMediator.destroyProcess.
+ * You can override this method to customize process handler creation.
+ * @return
+ */
+ protected ProcessHandler createProcessHandler(@NotNull Process process, @NotNull GeneralCommandLine commandLine) {
+ return new CustomDestroyProcessHandler(process, commandLine);
}
- private static int getProcessPid(Process proc) {
- try {
- Field f = proc.getClass().getDeclaredField("pid");
- f.setAccessible(true);
- int pid = ((Number)f.get(proc)).intValue();
- return pid;
- }
- catch (NoSuchFieldException e) {
- throw new IllegalStateException("system is not linux", e);
- }
- catch (IllegalAccessException e) {
- throw new IllegalStateException("system is not linux", e);
- }
- }
- private static String[] getCmd() {
- if (SystemInfo.isLinux) {
- return new String[]{"ps", "e", "--format", "%P%p%a"};
- }
- else if (SystemInfo.isMac) {
- return new String[]{"ps", "-ax", "-E", "-o", "ppid,pid,command"};
+ private String getRunnerPath() {
+ if (File.separatorChar == '\\') {
+ return RunnerMediatorManager.getInstance().getRunnerPath();
}
else {
- throw new IllegalStateException(System.getProperty("os.name") + " is not supported.");
+ throw new IllegalStateException("There is no need of runner under unix based OS");
}
}
- private static boolean containsMarker(@NotNull String environ, @NotNull String uid) {
- return environ.contains(uid);
- }
-
- @NotNull
- public static String readProcEnviron(int child_pid) throws FileNotFoundException {
- StringBuffer res = new StringBuffer();
- Scanner s = new Scanner(new File("/proc/" + child_pid + "/environ"));
- while (s.hasNextLine()) {
- res.append(s).append("\n");
- }
- return res.toString();
+ private void injectRunnerCommand(@NotNull GeneralCommandLine commandLine) {
+ commandLine.getParametersList().addAt(0, commandLine.getExePath());
+ commandLine.setExePath(getRunnerPath());
}
- private static void sendSignal(int pid, int signal) {
- C_LIB.kill(pid, signal);
+ public static boolean isUnix() {
+ return SystemInfo.isLinux || SystemInfo.isMac;
}
- interface CLib extends Library {
- int getpid();
-
- int kill(int pid, int signal);
+ public static boolean isWindows() {
+ if (File.separatorChar == '\\') {
+ return true;
+ }
+ return false;
}
- static CLib C_LIB;
-
- static {
+ /**
+ * Destroys process tree: in case of windows via imitating ctrl+break, in case of unix via sending sig_kill to every process in tree.
+ * @param process to kill with all subprocesses
+ */
+ public static boolean destroyProcess(Process process) {
try {
- if (!Platform.isWindows()) {
-
- C_LIB = ((CLib)Native.loadLibrary("c", CLib.class));
+ if (isWindows()) {
+ sendCtrlBreakThroughStream(process);
+ return true;
+ }
+ else if (isUnix()) {
+ UnixProcessManager.sendSigKillToProcessTree(process);
+ return true;
+ }
+ else {
+ return false;
}
}
catch (Exception e) {
- C_LIB = null;
+ LOG.error("Couldn't terminate the process", e);
+ return false;
}
}
+ /**
+ *
+ */
public static class CustomDestroyProcessHandler extends ColoredProcessHandler {
- private static final char IAC = (char)5;
- private static final char BRK = (char)3;
- private final String myCommand;
public CustomDestroyProcessHandler(@NotNull Process process,
@NotNull GeneralCommandLine commandLine) {
super(process, commandLine.getCommandLineString());
- myCommand = commandLine.getExePath();
}
@Override
protected void destroyProcessImpl() {
- if (!doCustomDestroy()) {
+ if (!RunnerMediator.destroyProcess(getProcess())) {
super.destroyProcessImpl();
}
}
-
- private boolean doCustomDestroy() {
- try {
- if (isWindows()) {
- sendCtrlBreakThroughStream();
- return true;
- }
- else if (canSendSignals()) {
- sendSigKill(getProcess());
- return true;
- }
- else {
- return false;
- }
- }
- catch (Exception e) {
- LOG.error("Couldn't terminate the process", e);
- return false;
- }
- }
-
- private void sendCtrlBreakThroughStream() {
- OutputStream os = getProcessInput();
- PrintWriter pw = new PrintWriter(os);
- try {
- pw.print(IAC);
- pw.print(BRK);
- pw.flush();
- }
- finally {
- pw.close();
- }
- }
- }
-
- private static class ProcessInfo {
-
- private ProcessInfo() {
- }
-
- private static Map<Integer, List<Integer>> BY_PARENT = Maps.newTreeMap(); // pid -> list of children pids
-
- public static void register(Integer pid, Integer parentPid) {
- List<Integer> children = BY_PARENT.get(parentPid);
- if (children == null) children = Lists.newLinkedList();
- children.add(pid);
- BY_PARENT.put(parentPid, children);
- }
-
- static void killProcTree(int pid, int signal) {
- List<Integer> children = BY_PARENT.get(pid);
- if (children != null) {
- for (int child : children) killProcTree(child, signal);
- }
- sendSignal(pid, signal);
- }
}
}
--- /dev/null
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * 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.
+ */
+package com.intellij.execution.process;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.intellij.openapi.util.SystemInfo;
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Platform;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.StringTokenizer;
+
+/**
+ * Utility class to terminate unix processes.
+ *
+ * @author traff
+ */
+public class UnixProcessManager {
+ public static final int SIGINT = 2;
+ public static final int SIGKILL = 9;
+
+ private static CLib C_LIB;
+
+ static {
+ try {
+ if (!Platform.isWindows()) {
+ C_LIB = ((CLib)Native.loadLibrary("c", CLib.class));
+ }
+ }
+ catch (Exception e) {
+ C_LIB = null;
+ }
+ }
+
+ private UnixProcessManager() {
+ }
+
+ public static void sendSignal(Process process, int signal) {
+ int process_pid = getProcessPid(process);
+ sendSignal(process_pid, signal);
+ }
+
+ public static void sendSignal(int pid, int signal) {
+ checkCLib();
+ C_LIB.kill(pid, signal);
+ }
+
+ private static void checkCLib() {
+ if (C_LIB == null) {
+ throw new IllegalStateException("System is not unix(couldn't load c library)");
+ }
+ }
+
+ public static void sendSigIntToProcessTree(Process process) {
+ sendSignalToProcessTree(process, SIGINT);
+ }
+
+ public static void sendSigKillToProcessTree(Process process) {
+ sendSignalToProcessTree(process, SIGKILL);
+ }
+
+ /**
+ * Sends signal to every child process of a tree root process
+ * @param process tree root process
+ *
+ */
+ public static void sendSignalToProcessTree(Process process, int signal) {
+ checkCLib();
+
+ int our_pid = C_LIB.getpid();
+ int process_pid = getProcessPid(process);
+
+ try {
+ String[] psCmd = getPSCmd();
+ Process p = Runtime.getRuntime().exec(psCmd);
+
+ ProcessInfo processInfo = new ProcessInfo();
+
+ @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
+ BufferedReader stdInput = new BufferedReader(new
+ InputStreamReader(p.getInputStream()));
+ BufferedReader stdError = new BufferedReader(new
+ InputStreamReader(p.getErrorStream()));
+ try {
+ String s;
+ stdInput.readLine(); //ps output header
+ int foundPid = 0;
+ while ((s = stdInput.readLine()) != null) {
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ int parent_pid = Integer.parseInt(st.nextToken());
+ int pid = Integer.parseInt(st.nextToken());
+
+ processInfo.register(pid, parent_pid);
+
+ if (pid == process_pid) {
+ if (parent_pid == our_pid) {
+ foundPid = pid;
+ }
+ else {
+ throw new IllegalStateException("process is not our child");
+ }
+ }
+ }
+
+ if (foundPid != 0) {
+ processInfo.killProcTree(foundPid, signal);
+ }
+ else {
+ throw new IllegalStateException("process not found: " + process_pid + ", idea pid =" + our_pid);
+ }
+
+ StringBuffer errorStr = new StringBuffer();
+ while ((s = stdError.readLine()) != null) {
+ errorStr.append(s).append("\n");
+ }
+ if (errorStr.length() > 0) {
+ throw new IllegalStateException("error:" + errorStr.toString());
+ }
+ }
+ finally {
+ stdInput.close();
+ stdError.close();
+ }
+ }
+ catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public static int getProcessPid(Process process) {
+ try {
+ Field f = process.getClass().getDeclaredField("pid");
+ f.setAccessible(true);
+ return ((Number)f.get(process)).intValue();
+ }
+ catch (NoSuchFieldException e) {
+ throw new IllegalStateException("system is not unix", e);
+ }
+ catch (IllegalAccessException e) {
+ throw new IllegalStateException("system is not unix", e);
+ }
+ }
+
+ public static String[] getPSCmd() {
+ if (SystemInfo.isLinux) {
+ return new String[]{"ps", "e", "--format", "%P%p%a"};
+ }
+ else if (SystemInfo.isMac) {
+ return new String[]{"ps", "-ax", "-E", "-o", "ppid,pid,command"};
+ }
+ else {
+ throw new IllegalStateException(System.getProperty("os.name") + " is not supported.");
+ }
+ }
+
+ public static boolean containsMarker(@NotNull String environ, @NotNull String uid) {
+ return environ.contains(uid);
+ }
+
+ @NotNull
+ public static String readProcEnviron(int child_pid) throws FileNotFoundException {
+ StringBuffer res = new StringBuffer();
+ Scanner s = new Scanner(new File("/proc/" + child_pid + "/environ"));
+ while (s.hasNextLine()) {
+ res.append(s).append("\n");
+ }
+ return res.toString();
+ }
+
+
+ public interface CLib extends Library {
+ int getpid();
+
+ int kill(int pid, int signal);
+ }
+
+ private static class ProcessInfo {
+ private Map<Integer, List<Integer>> BY_PARENT = Maps.newTreeMap(); // pid -> list of children pids
+
+ public void register(Integer pid, Integer parentPid) {
+ List<Integer> children = BY_PARENT.get(parentPid);
+ if (children == null) children = Lists.newLinkedList();
+ children.add(pid);
+ BY_PARENT.put(parentPid, children);
+ }
+
+ void killProcTree(int pid, int signal) {
+ List<Integer> children = BY_PARENT.get(pid);
+ if (children != null) {
+ for (int child : children) killProcTree(child, signal);
+ }
+ sendSignal(pid, signal);
+ }
+ }
+}