/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.util; import com.sun.jna.Memory; import com.sun.jna.Native; import static com.sun.jna.Pointer.NULL; import com.sun.jna.ptr.IntByReference; 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 static hudson.util.jna.GNUCLibrary.LIBC; import hudson.util.ProcessTreeRemoting.IOSProcess; import hudson.util.ProcessTreeRemoting.IProcessTree; import org.apache.commons.io.FileUtils; import org.jvnet.winp.WinProcess; import org.jvnet.winp.WinpException; 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.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.Arrays; import java.util.logging.Level; import static java.util.logging.Level.FINER; import static java.util.logging.Level.FINEST; import java.util.logging.Logger; /** * 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() {} /** * 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.fine("killAll: 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.log(Level.WARNING, "Failed to obtain killers",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.log(Level.WARNING, "Failed to kill pid="+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. */ public static ProcessTree get() { if(!enabled) return DEFAULT; try { if(File.pathSeparatorChar==';') return new Windows(); 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(); } catch (LinkageError e) { LOGGER.log(Level.WARNING,"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 { Windows() { for (final WinProcess p : WinProcess.all()) { 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.finer("Killing recursively "+getPid()); p.killRecursively(); } public void kill() throws InterruptedException { LOGGER.finer("Killing "+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; } }); } } @Override public OSProcess get(Process proc) { return get(new WinProcess(proc).getPid()); } public void killAll(Map<String, String> modelEnvVars) throws InterruptedException { for( OSProcess p : this) { if(p.getPid()<10) continue; // ignore system processes like "idle process" LOGGER.finest("Considering to kill "+p.getPid()); boolean matched; try { matched = p.hasMatchingEnvVars(modelEnvVars); } catch (WinpException e) { // likely a missing privilege LOGGER.log(FINEST," Failed to check environment variable match",e); continue; } if(matched) p.killRecursively(); else LOGGER.finest("Environment variable didn't match"); } } static { WinProcess.enableDebugPrivilege(); } } 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.fine("Killing pid="+pid); UnixReflection.DESTROY_PROCESS.invoke(null, 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.log(Level.INFO, "Failed to terminate pid="+getPid(),e); } killByKiller(); } public void killRecursively() throws InterruptedException { LOGGER.fine("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); 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 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.isLoggable(FINER)) LOGGER.finer("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.isLoggable(FINER)) LOGGER.finer("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.isLoggable(FINEST)) LOGGER.finest("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.isLoggable(FINEST)) LOGGER.finest(prefix +" is so far "+buf.toString()); buf.write(ch); } String line = buf.toString(); if(LOGGER.isLoggable(FINEST)) LOGGER.finest(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 { Darwin() { try { IntByReference _ = new IntByReference(sizeOfInt); IntByReference size = new IntByReference(sizeOfInt); Memory m; int nRetry = 0; while(true) { // find out how much memory we need to do this if(LIBC.sysctl(MIB_PROC_ALL,3, NULL, size, NULL, _)!=0) throw new IOException("Failed to obtain memory requirement: "+LIBC.strerror(Native.getLastError())); // now try the real call m = new Memory(size.getValue()); if(LIBC.sysctl(MIB_PROC_ALL,3, m, size, NULL, _)!=0) { if(Native.getLastError()==ENOMEM && nRetry++<16) continue; // retry throw new IOException("Failed to call kern.proc.all: "+LIBC.strerror(Native.getLastError())); } break; } int count = size.getValue()/sizeOf_kinfo_proc; LOGGER.fine("Found "+count+" processes"); for( int base=0; base<size.getValue(); base+=sizeOf_kinfo_proc) { int pid = m.getInt(base+24); int ppid = m.getInt(base+416); // int effective_uid = m.getInt(base+304); // byte[] comm = new byte[16]; // m.read(base+163,comm,0,16); super.processes.put(pid,new DarwinProcess(pid,ppid)); } } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to obtain process list",e); } } private class DarwinProcess extends UnixProcess { private final int ppid; private EnvVars envVars; private List<String> arguments; DarwinProcess(int pid, int ppid) { super(pid); this.ppid = ppid; } public OSProcess getParent() { return get(ppid); } public synchronized EnvVars getEnvironmentVariables() { if(envVars !=null) return envVars; parse(); return envVars; } public List<String> getArguments() { if(arguments !=null) return arguments; parse(); return arguments; } private void parse() { try { // allocate them first, so that the parse error wil result in empty data // and avoid retry. arguments = new ArrayList<String>(); envVars = new EnvVars(); IntByReference _ = new IntByReference(); IntByReference argmaxRef = new IntByReference(0); IntByReference size = new IntByReference(sizeOfInt); // for some reason, I was never able to get sysctlbyname work. // if(LIBC.sysctlbyname("kern.argmax", argmaxRef.getPointer(), size, NULL, _)!=0) if(LIBC.sysctl(new int[]{CTL_KERN,KERN_ARGMAX},2, argmaxRef.getPointer(), size, NULL, _)!=0) throw new IOException("Failed to get kernl.argmax: "+LIBC.strerror(Native.getLastError())); int argmax = argmaxRef.getValue(); class StringArrayMemory extends Memory { private long offset=0; StringArrayMemory(long l) { super(l); } int readInt() { int r = getInt(offset); offset+=sizeOfInt; return r; } byte peek() { return getByte(offset); } String readString() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte ch; while((ch = getByte(offset++))!='\0') baos.write(ch); return baos.toString(); } void skip0() { // skip trailing '\0's while(getByte(offset)=='\0') offset++; } } StringArrayMemory m = new StringArrayMemory(argmax); size.setValue(argmax); if(LIBC.sysctl(new int[]{CTL_KERN,KERN_PROCARGS2,pid},3, m, size, NULL, _)!=0) throw new IOException("Failed to obtain ken.procargs2: "+LIBC.strerror(Native.getLastError())); /* * Make a sysctl() call to get the raw argument space of the * process. The layout is documented in start.s, which is part * of the Csu project. In summary, it looks like: * * /---------------\ 0x00000000 * : : * : : * |---------------| * | argc | * |---------------| * | arg[0] | * |---------------| * : : * : : * |---------------| * | arg[argc - 1] | * |---------------| * | 0 | * |---------------| * | env[0] | * |---------------| * : : * : : * |---------------| * | env[n] | * |---------------| * | 0 | * |---------------| <-- Beginning of data returned by sysctl() * | exec_path | is here. * |:::::::::::::::| * | | * | String area. | * | | * |---------------| <-- Top of stack. * : : * : : * \---------------/ 0xffffffff */ int nargs = m.readInt(); m.readString(); // exec path for( int i=0; i<nargs; i++) { m.skip0(); arguments.add(m.readString()); } // this is how you can read environment variables while(m.peek()!=0) envVars.addLine(m.readString()); } catch (IOException e) { // this happens with insufficient permissions, so just ignore the problem. } } } // local constants private static final int sizeOf_kinfo_proc = 492; // TODO:checked on 32bit Mac OS X. is this different on 64bit? private static final int sizeOfInt = Native.getNativeSize(int.class); private static final int CTL_KERN = 1; private static final int KERN_PROC = 14; private static final int KERN_PROC_ALL = 0; private static final int ENOMEM = 12; private static int[] MIB_PROC_ALL = {CTL_KERN, KERN_PROC, KERN_PROC_ALL}; private static final int KERN_ARGMAX = 8; private static final int KERN_PROCARGS2 = 49; } /** * 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")); private static final Logger LOGGER = Logger.getLogger(ProcessTree.class.getName()); /** * 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"); }