/*******************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi
*
*
*******************************************************************************/
package hudson.util;
import hudson.EnvVars;
import hudson.Util;
import hudson.model.Hudson;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
import hudson.slaves.SlaveComputer;
import hudson.util.ProcessTree.OSProcess;
import hudson.util.ProcessTreeRemoting.IOSProcess;
import hudson.util.ProcessTreeRemoting.IProcessTree;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.StringTokenizer;
import org.apache.commons.io.FileUtils;
import org.eclipse.hudson.jna.NativeAccessException;
import org.eclipse.hudson.jna.NativeProcess;
import org.eclipse.hudson.jna.NativeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents a snapshot of the process tree of the current system.
*
* <p> A {@link ProcessTree} is really conceptually a map from process ID to a
* {@link OSProcess} object. When Hudson runs on platforms that support process
* introspection, this allows you to introspect and do some useful things on
* processes. On other platforms, the implementation falls back to "do nothing"
* behavior.
*
* <p> {@link ProcessTree} is remotable.
*
* @author Kohsuke Kawaguchi
* @since 1.315
*/
public abstract class ProcessTree implements Iterable<OSProcess>, IProcessTree, Serializable {
/**
* To be filled in the constructor of the derived type.
*/
protected final Map<Integer/*pid*/, OSProcess> processes = new HashMap<Integer, OSProcess>();
/**
* Lazily obtained {@link ProcessKiller}s to be applied on this process
* tree.
*/
private transient volatile List<ProcessKiller> killers;
// instantiation only allowed for subtypes in this class
private ProcessTree() {
}
private static Logger logger = LoggerFactory.getLogger(ProcessTree.class);
/**
* Gets the process given a specific ID, or null if no such process exists.
*/
public final OSProcess get(int pid) {
return processes.get(pid);
}
/**
* Lists all the processes in the system.
*/
public final Iterator<OSProcess> iterator() {
return processes.values().iterator();
}
/**
* Try to convert {@link Process} into this process object or null if it
* fails (for example, maybe the snapshot is taken after this process has
* already finished.)
*/
public abstract OSProcess get(Process proc);
/**
* Kills all the processes that have matching environment variables.
*
* <p> In this method, the method is given a "model environment variables",
* which is a list of environment variables and their values that are
* characteristic to the launched process. The implementation is expected to
* find processes in the system that inherit these environment variables,
* and kill them all. This is suitable for locating daemon processes that
* cannot be tracked by the regular ancestor/descendant relationship.
*/
public abstract void killAll(Map<String, String> modelEnvVars) throws InterruptedException;
/**
* Convenience method that does {@link #killAll(Map)} and
* {@link OSProcess#killRecursively()}. This is necessary to reliably kill
* the process and its descendants, as some OS may not implement
* {@link #killAll(Map)}.
*
* Either of the parameter can be null.
*/
public void killAll(Process proc, Map<String, String> modelEnvVars) throws InterruptedException {
logger.info("killAll (process started by build): process=" + proc + " and envs=" + modelEnvVars);
OSProcess p = get(proc);
if (p != null) {
p.killRecursively();
}
if (modelEnvVars != null) {
killAll(modelEnvVars);
}
}
/**
* Obtains the list of killers.
*/
/*package*/ final List<ProcessKiller> getKillers() throws InterruptedException {
if (killers == null) {
try {
killers = SlaveComputer.getChannelToMaster().call(new Callable<List<ProcessKiller>, IOException>() {
public List<ProcessKiller> call() throws IOException {
return new ArrayList<ProcessKiller>(ProcessKiller.all());
}
});
} catch (IOException e) {
logger.info("Failed to obtain killers (process started by build) ", e);
killers = Collections.emptyList();
}
}
return killers;
}
/**
* Represents a process.
*/
public abstract class OSProcess implements IOSProcess, Serializable {
final int pid;
// instantiation only allowed for subtypes in this class
private OSProcess(int pid) {
this.pid = pid;
}
public final int getPid() {
return pid;
}
/**
* Gets the parent process. This method may return null, because there's
* no guarantee that we are getting a consistent snapshot of the whole
* system state.
*/
public abstract OSProcess getParent();
/*package*/ final ProcessTree getTree() {
return ProcessTree.this;
}
/**
* Immediate child processes.
*/
public final List<OSProcess> getChildren() {
List<OSProcess> r = new ArrayList<OSProcess>();
for (OSProcess p : ProcessTree.this) {
if (p.getParent() == this) {
r.add(p);
}
}
return r;
}
/**
* Kills this process.
*/
public abstract void kill() throws InterruptedException;
void killByKiller() throws InterruptedException {
for (ProcessKiller killer : getKillers()) {
try {
if (killer.kill(this)) {
break;
}
} catch (IOException e) {
logger.info("Failed to kill the process started by process - " + getPid(), e);
}
}
}
/**
* Kills this process and all the descendants. <p> Note that the notion
* of "descendants" is somewhat vague, in the presence of such things
* like daemons. On platforms where the recursive operation is not
* supported, this just kills the current process.
*/
public abstract void killRecursively() throws InterruptedException;
/**
* Gets the command-line arguments of this process.
*
* <p> On Windows, where the OS models command-line arguments as a
* single string, this method computes the approximated tokenization.
*/
public abstract List<String> getArguments();
/**
* Obtains the environment variables of this process.
*
* @return empty map if failed (for example because the process is
* already dead, or the permission was denied.)
*/
public abstract EnvVars getEnvironmentVariables();
/**
* Given the environment variable of a process and the "model
* environment variable" that Hudson used for launching the build,
* returns true if there's a match (which means the process should be
* considered a descendant of a build.)
*/
public final boolean hasMatchingEnvVars(Map<String, String> modelEnvVar) {
if (modelEnvVar.isEmpty()) // sanity check so that we don't start rampage.
{
return false;
}
SortedMap<String, String> envs = getEnvironmentVariables();
for (Entry<String, String> e : modelEnvVar.entrySet()) {
String v = envs.get(e.getKey());
if (v == null || !v.equals(e.getValue())) {
return false; // no match
}
}
return true;
}
/**
* Executes a chunk of code at the same machine where this process
* resides.
*/
public <T> T act(ProcessCallable<T> callable) throws IOException, InterruptedException {
return callable.invoke(this, Hudson.MasterComputer.localChannel);
}
Object writeReplace() {
return new SerializedProcess(pid);
}
}
/**
* Serialized form of {@link OSProcess} is the PID and {@link ProcessTree}
*/
private final class SerializedProcess implements Serializable {
private final int pid;
private static final long serialVersionUID = 1L;
private SerializedProcess(int pid) {
this.pid = pid;
}
Object readResolve() {
return get(pid);
}
}
/**
* Code that gets executed on the machine where the {@link OSProcess} is
* local. Used to act on {@link OSProcess}.
*
* @see OSProcess#act(ProcessCallable)
*/
public static interface ProcessCallable<T> extends Serializable {
/**
* Performs the computational task on the node where the data is
* located.
*
* @param process {@link OSProcess} that represents the local process.
* @param channel The "back pointer" of the {@link Channel} that
* represents the communication with the node from where the code was
* sent.
*/
T invoke(OSProcess process, VirtualChannel channel) throws IOException;
}
/**
* Gets the {@link ProcessTree} of the current system that JVM runs in, or
* in the worst case return the default one that's not capable of killing
* descendants at all.
*
* @return ProcessTree of the current system.
*/
public static ProcessTree get() {
return get(NativeUtils.getInstance());
}
/**
* Gets the {@link ProcessTree} of the current system that JVM runs in, or
* in the worst case return the default one that's not capable of killing
* descendants at all.
*
* @param nativeUtils instance of NativeUtils.
* @return ProcessTree of the current system.
*/
public static ProcessTree get(NativeUtils nativeUtils) {
if (!enabled) {
return DEFAULT;
}
try {
if (File.pathSeparatorChar == ';') {
return new Windows(nativeUtils);
}
String os = Util.fixNull(System.getProperty("os.name"));
if (os.equals("Linux")) {
return new Linux();
}
if (os.equals("SunOS")) {
return new Solaris();
}
if (os.equals("Mac OS X")) {
return new Darwin(nativeUtils);
}
} catch (LinkageError e) {
logger.warn("Failed to load winp. Reverting to the default", e);
enabled = false;
}
return DEFAULT;
}
//
//
// implementation follows
//-------------------------------------------
//
/**
* Empty process list as a default value if the platform doesn't support it.
*/
private static final ProcessTree DEFAULT = new Local() {
public OSProcess get(final Process proc) {
return new OSProcess(-1) {
public OSProcess getParent() {
return null;
}
public void killRecursively() {
// fall back to a single process killer
proc.destroy();
}
public void kill() throws InterruptedException {
proc.destroy();
killByKiller();
}
public List<String> getArguments() {
return Collections.emptyList();
}
public EnvVars getEnvironmentVariables() {
return new EnvVars();
}
};
}
public void killAll(Map<String, String> modelEnvVars) {
// no-op
}
};
private static final class Windows extends Local {
private NativeUtils nativeUtils;
Windows(NativeUtils nativeUtils) {
this.nativeUtils = nativeUtils;
try {
for (final NativeProcess p : nativeUtils.getWindowsProcesses()) {
int pid = p.getPid();
super.processes.put(pid, new OSProcess(pid) {
private EnvVars env;
private List<String> args;
public OSProcess getParent() {
// windows process doesn't have parent/child relationship
return null;
}
public void killRecursively() {
logger.debug("Killing recursively process started by build" + getPid());
p.killRecursively();
}
public void kill() throws InterruptedException {
logger.debug("Killing process started by build" + getPid());
p.kill();
killByKiller();
}
@Override
public synchronized List<String> getArguments() {
if (args == null) {
args = Arrays.asList(QuotedStringTokenizer.tokenize(p.getCommandLine()));
}
return args;
}
@Override
public synchronized EnvVars getEnvironmentVariables() {
if (env == null) {
env = new EnvVars(p.getEnvironmentVariables());
}
return env;
}
});
}
} catch (NativeAccessException exc) {
logger.warn("Failed to fetch Windows Native Processes", exc);
}
}
@Override
public OSProcess get(Process proc) {
try {
return get(nativeUtils.getWindowsProcessId(proc));
} catch (NativeAccessException exc) {
logger.warn("Failed to get Windows Native Process", exc);
}
return null;
}
public void killAll(Map<String, String> modelEnvVars) throws InterruptedException {
for (OSProcess p : this) {
if (p.getPid() < 10) {
continue; // ignore system processes like "idle process"
}
boolean matched;
try {
matched = p.hasMatchingEnvVars(modelEnvVars);
} catch (NativeAccessException e) {
// likely a missing privilege
logger.debug(" Failed to check environment variable match", e);
continue;
}
if (matched) {
logger.debug("Killing process started by build " + p.getPid());
p.killRecursively();
}
}
}
}
static abstract class Unix extends Local {
@Override
public OSProcess get(Process proc) {
try {
return get((Integer) UnixReflection.PID_FIELD.get(proc));
} catch (IllegalAccessException e) { // impossible
IllegalAccessError x = new IllegalAccessError();
x.initCause(e);
throw x;
}
}
public void killAll(Map<String, String> modelEnvVars) throws InterruptedException {
for (OSProcess p : this) {
if (p.hasMatchingEnvVars(modelEnvVars)) {
p.killRecursively();
}
}
}
}
/**
* {@link ProcessTree} based on /proc.
*/
static abstract class ProcfsUnix extends Unix {
ProcfsUnix() {
File[] processes = new File("/proc").listFiles(new FileFilter() {
public boolean accept(File f) {
return f.isDirectory();
}
});
if (processes == null) {
logger.info("No /proc");
return;
}
for (File p : processes) {
int pid;
try {
pid = Integer.parseInt(p.getName());
} catch (NumberFormatException e) {
// other sub-directories
continue;
}
try {
this.processes.put(pid, createProcess(pid));
} catch (IOException e) {
// perhaps the process status has changed since we obtained a directory listing
}
}
}
protected abstract OSProcess createProcess(int pid) throws IOException;
}
/**
* A process.
*/
public abstract class UnixProcess extends OSProcess {
protected UnixProcess(int pid) {
super(pid);
}
protected final File getFile(String relativePath) {
return new File(new File("/proc/" + getPid()), relativePath);
}
/**
* Tries to kill this process.
*/
public void kill() throws InterruptedException {
try {
int pid = getPid();
logger.debug("Killing pid=" + pid);
UnixReflection.destroyProcesses(pid);
} catch (IllegalAccessException e) {
// this is impossible
IllegalAccessError x = new IllegalAccessError();
x.initCause(e);
throw x;
} catch (InvocationTargetException e) {
// tunnel serious errors
if (e.getTargetException() instanceof Error) {
throw (Error) e.getTargetException();
}
// otherwise log and let go. I need to see when this happens
logger.info("Failed to terminate pid=" + getPid(), e);
}
killByKiller();
}
public void killRecursively() throws InterruptedException {
logger.debug("Recursively killing pid=" + getPid());
for (OSProcess p : getChildren()) {
p.killRecursively();
}
kill();
}
/**
* Obtains the argument list of this process.
*
* @return empty list if failed (for example because the process is
* already dead, or the permission was denied.)
*/
public abstract List<String> getArguments();
}
/**
* Reflection used in the Unix support.
*/
private static final class UnixReflection {
/**
* Field to access the PID of the process.
*/
private static final Field PID_FIELD;
/**
* Method to destroy a process, given pid.
*/
private static final Method DESTROY_PROCESS;
static {
try {
Class<?> clazz = Class.forName("java.lang.UNIXProcess");
PID_FIELD = clazz.getDeclaredField("pid");
PID_FIELD.setAccessible(true);
if (isJava8OrLater()) {
// In JDK 8, the signature changed to destroyProcess(int pid, boolean force);
DESTROY_PROCESS = clazz.getDeclaredMethod("destroyProcess", int.class, boolean.class);
} else {
DESTROY_PROCESS = clazz.getDeclaredMethod("destroyProcess", int.class);
}
DESTROY_PROCESS.setAccessible(true);
} catch (ClassNotFoundException e) {
LinkageError x = new LinkageError();
x.initCause(e);
throw x;
} catch (NoSuchFieldException e) {
LinkageError x = new LinkageError();
x.initCause(e);
throw x;
} catch (NoSuchMethodException e) {
LinkageError x = new LinkageError();
x.initCause(e);
throw x;
}
}
static void destroyProcesses(int pid) throws IllegalAccessException, InvocationTargetException {
if (isJava8OrLater()) {
DESTROY_PROCESS.invoke(null, pid, true);
} else {
DESTROY_PROCESS.invoke(null, pid);
}
}
private static boolean isJava8OrLater(){
String javaVersionStr = System.getProperty("java.version");
String[] javaVersionElements = javaVersionStr.split("\\.");
int majorJavaVersion = Integer.parseInt(javaVersionElements[1]);
return majorJavaVersion > 7;
}
}
static class Linux extends ProcfsUnix {
protected LinuxProcess createProcess(int pid) throws IOException {
return new LinuxProcess(pid);
}
class LinuxProcess extends UnixProcess {
private int ppid = -1;
private EnvVars envVars;
private List<String> arguments;
LinuxProcess(int pid) throws IOException {
super(pid);
BufferedReader r = new BufferedReader(new FileReader(getFile("status")));
try {
String line;
while ((line = r.readLine()) != null) {
line = line.toLowerCase(Locale.ENGLISH);
if (line.startsWith("ppid:")) {
ppid = Integer.parseInt(line.substring(5).trim());
break;
}
}
} finally {
r.close();
}
if (ppid == -1) {
throw new IOException("Failed to parse PPID from /proc/" + pid + "/status");
}
}
public OSProcess getParent() {
return get(ppid);
}
public synchronized List<String> getArguments() {
if (arguments != null) {
return arguments;
}
arguments = new ArrayList<String>();
try {
byte[] cmdline = FileUtils.readFileToByteArray(getFile("cmdline"));
int pos = 0;
for (int i = 0; i < cmdline.length; i++) {
byte b = cmdline[i];
if (b == 0) {
arguments.add(new String(cmdline, pos, i - pos));
pos = i + 1;
}
}
} catch (IOException e) {
// failed to read. this can happen under normal circumstances (most notably permission denied)
// so don't report this as an error.
}
arguments = Collections.unmodifiableList(arguments);
return arguments;
}
public synchronized EnvVars getEnvironmentVariables() {
if (envVars != null) {
return envVars;
}
envVars = new EnvVars();
try {
byte[] environ = FileUtils.readFileToByteArray(getFile("environ"));
int pos = 0;
for (int i = 0; i < environ.length; i++) {
byte b = environ[i];
if (b == 0) {
envVars.addLine(new String(environ, pos, i - pos));
pos = i + 1;
}
}
} catch (IOException e) {
// failed to read. this can happen under normal circumstances (most notably permission denied)
// so don't report this as an error.
}
return envVars;
}
}
}
/**
* Implementation for Solaris that uses <tt>/proc</tt>.
*
* Amazingly, this single code works for both 32bit and 64bit Solaris,
* despite the fact that does a lot of pointer manipulation and what not.
*/
static class Solaris extends ProcfsUnix {
protected OSProcess createProcess(final int pid) throws IOException {
return new SolarisProcess(pid);
}
private class SolarisProcess extends UnixProcess {
private final int ppid;
/**
* Address of the environment vector. Even on 64bit Solaris this is
* still 32bit pointer.
*/
private final int envp;
/**
* Similarly, address of the arguments vector.
*/
private final int argp;
private final int argc;
private EnvVars envVars;
private List<String> arguments;
private SolarisProcess(int pid) throws IOException {
super(pid);
RandomAccessFile psinfo = new RandomAccessFile(getFile("psinfo"), "r");
try {
// see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/procfs.h
//typedef struct psinfo {
// int pr_flag; /* process flags */
// int pr_nlwp; /* number of lwps in the process */
// pid_t pr_pid; /* process id */
// pid_t pr_ppid; /* process id of parent */
// pid_t pr_pgid; /* process id of process group leader */
// pid_t pr_sid; /* session id */
// uid_t pr_uid; /* real user id */
// uid_t pr_euid; /* effective user id */
// gid_t pr_gid; /* real group id */
// gid_t pr_egid; /* effective group id */
// uintptr_t pr_addr; /* address of process */
// size_t pr_size; /* size of process image in Kbytes */
// size_t pr_rssize; /* resident set size in Kbytes */
// dev_t pr_ttydev; /* controlling tty device (or PRNODEV) */
// ushort_t pr_pctcpu; /* % of recent cpu time used by all lwps */
// ushort_t pr_pctmem; /* % of system memory used by process */
// timestruc_t pr_start; /* process start time, from the epoch */
// timestruc_t pr_time; /* cpu time for this process */
// timestruc_t pr_ctime; /* cpu time for reaped children */
// char pr_fname[PRFNSZ]; /* name of exec'ed file */
// char pr_psargs[PRARGSZ]; /* initial characters of arg list */
// int pr_wstat; /* if zombie, the wait() status */
// int pr_argc; /* initial argument count */
// uintptr_t pr_argv; /* address of initial argument vector */
// uintptr_t pr_envp; /* address of initial environment vector */
// char pr_dmodel; /* data model of the process */
// lwpsinfo_t pr_lwp; /* information for representative lwp */
//} psinfo_t;
// see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/types.h
// for the size of the various datatype.
// see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/cmd/ptools/pargs/pargs.c
// for how to read this information
psinfo.seek(8);
if (adjust(psinfo.readInt()) != pid) {
throw new IOException("psinfo PID mismatch"); // sanity check
}
ppid = adjust(psinfo.readInt());
psinfo.seek(188); // now jump to pr_argc
argc = adjust(psinfo.readInt());
argp = adjust(psinfo.readInt());
envp = adjust(psinfo.readInt());
} finally {
psinfo.close();
}
if (ppid == -1) {
throw new IOException("Failed to parse PPID from /proc/" + pid + "/status");
}
}
public OSProcess getParent() {
return get(ppid);
}
public synchronized List<String> getArguments() {
if (arguments != null) {
return arguments;
}
arguments = new ArrayList<String>(argc);
try {
RandomAccessFile as = new RandomAccessFile(getFile("as"), "r");
if (logger.isTraceEnabled()) {
logger.debug("Reading " + getFile("as"));
}
try {
for (int n = 0; n < argc; n++) {
// read a pointer to one entry
as.seek(to64(argp + n * 4));
int p = adjust(as.readInt());
arguments.add(readLine(as, p, "argv[" + n + "]"));
}
} finally {
as.close();
}
} catch (IOException e) {
// failed to read. this can happen under normal circumstances (most notably permission denied)
// so don't report this as an error.
}
arguments = Collections.unmodifiableList(arguments);
return arguments;
}
public synchronized EnvVars getEnvironmentVariables() {
if (envVars != null) {
return envVars;
}
envVars = new EnvVars();
try {
RandomAccessFile as = new RandomAccessFile(getFile("as"), "r");
if (logger.isTraceEnabled()) {
logger.debug("Reading " + getFile("as"));
}
try {
for (int n = 0;; n++) {
// read a pointer to one entry
as.seek(to64(envp + n * 4));
int p = adjust(as.readInt());
if (p == 0) {
break; // completed the walk
}
// now read the null-terminated string
envVars.addLine(readLine(as, p, "env[" + n + "]"));
}
} finally {
as.close();
}
} catch (IOException e) {
// failed to read. this can happen under normal circumstances (most notably permission denied)
// so don't report this as an error.
}
return envVars;
}
private String readLine(RandomAccessFile as, int p, String prefix) throws IOException {
if (logger.isTraceEnabled()) {
logger.debug("Reading " + prefix + " at " + p);
}
as.seek(to64(p));
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int ch, i = 0;
while ((ch = as.read()) > 0) {
if ((++i) % 100 == 0 && logger.isTraceEnabled()) {
logger.debug(prefix + " is so far " + buf.toString());
}
buf.write(ch);
}
String line = buf.toString();
if (logger.isTraceEnabled()) {
logger.debug(prefix + " was " + line);
}
return line;
}
}
/**
* int to long conversion with zero-padding.
*/
private static long to64(int i) {
return i & 0xFFFFFFFFL;
}
/**
* {@link DataInputStream} reads a value in big-endian, so convert it to
* the correct value on little-endian systems.
*/
private static int adjust(int i) {
if (IS_LITTLE_ENDIAN) {
return (i << 24) | ((i << 8) & 0x00FF0000) | ((i >> 8) & 0x0000FF00) | (i >>> 24);
} else {
return i;
}
}
}
/**
* Implementation for Mac OS X based on sysctl(3).
*/
private static class Darwin extends Unix {
private NativeUtils nativeUtils;
Darwin(NativeUtils nativeUtils) {
this.nativeUtils = nativeUtils;
try {
for (final NativeProcess process : nativeUtils.getMacProcesses()) {
try {
super.processes.put(process.getPid(), new DarwinProcess(process));
} catch (NativeAccessException exc) {
// We may have access permission, skip and get rest of the process
logger.warn("Failed to fecth Native Mac Processes", exc.getLocalizedMessage());
}
}
} catch (NativeAccessException exc) {
// Don't fail if the Native Mac access plugin is not installed.
logger.warn("Failed to fecth Native Mac Processes", exc.getLocalizedMessage());
}
}
private class DarwinProcess extends UnixProcess {
NativeProcess process;
private DarwinProcess(NativeProcess process) {
super(process.getPid());
this.process = process;
}
public OSProcess getParent() {
return get(process.getPpid());
}
public synchronized EnvVars getEnvironmentVariables() {
EnvVars envVars = new EnvVars();
for (String key : process.getEnvironmentVariables().keySet()) {
envVars.put(key, process.getEnvironmentVariables().get(key));
}
return envVars;
}
public List<String> getArguments() {
List<String> arguments = new ArrayList<String>();
StringTokenizer tokenizer = new StringTokenizer(process.getCommandLine());
// Skip the exec name
tokenizer.nextToken();
while (tokenizer.hasMoreTokens()) {
arguments.add(tokenizer.nextToken());
}
return arguments;
}
}
}
/**
* Represents a local process tree, where this JVM and the process tree run
* on the same system. (The opposite of {@link Remote}.)
*/
public static abstract class Local extends ProcessTree {
Local() {
}
}
/**
* Represents a process tree over a channel.
*/
public static class Remote extends ProcessTree implements Serializable {
private final IProcessTree proxy;
public Remote(ProcessTree proxy, Channel ch) {
this.proxy = ch.export(IProcessTree.class, proxy);
for (Entry<Integer, OSProcess> e : proxy.processes.entrySet()) {
processes.put(e.getKey(), new RemoteProcess(e.getValue(), ch));
}
}
@Override
public OSProcess get(Process proc) {
return null;
}
@Override
public void killAll(Map<String, String> modelEnvVars) throws InterruptedException {
proxy.killAll(modelEnvVars);
}
Object writeReplace() {
return this; // cancel out super.writeReplace()
}
private static final long serialVersionUID = 1L;
private class RemoteProcess extends OSProcess implements Serializable {
private final IOSProcess proxy;
RemoteProcess(OSProcess proxy, Channel ch) {
super(proxy.getPid());
this.proxy = ch.export(IOSProcess.class, proxy);
}
public OSProcess getParent() {
IOSProcess p = proxy.getParent();
if (p == null) {
return null;
}
return get(p.getPid());
}
public void kill() throws InterruptedException {
proxy.kill();
}
public void killRecursively() throws InterruptedException {
proxy.killRecursively();
}
public List<String> getArguments() {
return proxy.getArguments();
}
public EnvVars getEnvironmentVariables() {
return proxy.getEnvironmentVariables();
}
Object writeReplace() {
return this; // cancel out super.writeReplace()
}
public <T> T act(ProcessCallable<T> callable) throws IOException, InterruptedException {
return proxy.act(callable);
}
private static final long serialVersionUID = 1L;
}
}
/**
* Use {@link Remote} as the serialized form.
*/
/*package*/ Object writeReplace() {
return new Remote(this, Channel.current());
}
// public static void main(String[] args) {
// // dump everything
// LOGGER.setLevel(Level.ALL);
// ConsoleHandler h = new ConsoleHandler();
// h.setLevel(Level.ALL);
// LOGGER.addHandler(h);
//
// Solaris killer = (Solaris)get();
// Solaris.SolarisSystem s = killer.createSystem();
// Solaris.SolarisProcess p = s.get(Integer.parseInt(args[0]));
// System.out.println(p.getEnvVars());
//
// if(args.length==2)
// p.kill();
// }
/*
On MacOS X, there's no procfs <http://www.osxbook.com/book/bonus/chapter11/procfs/>
instead you'd do it with the sysctl <http://search.cpan.org/src/DURIST/Proc-ProcessTable-0.42/os/darwin.c>
<http://developer.apple.com/documentation/Darwin/Reference/ManPages/man3/sysctl.3.html>
There's CLI but that doesn't seem to offer the access to per-process info
<http://developer.apple.com/documentation/Darwin/Reference/ManPages/man8/sysctl.8.html>
On HP-UX, pstat_getcommandline get you command line, but I'm not seeing any environment variables.
*/
private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian"));
/**
* Flag to control this feature.
*
* <p> This feature involves some native code, so we are allowing the user
* to disable this in case there's a fatal problem.
*
* <p> This property supports two names for a compatibility reason.
*/
public static boolean enabled = !Boolean.getBoolean(ProcessTreeKiller.class.getName() + ".disable")
&& !Boolean.getBoolean(ProcessTree.class.getName() + ".disable");
}