PageRenderTime 138ms CodeModel.GetById 84ms app.highlight 45ms RepoModel.GetById 1ms app.codeStats 0ms

/hudson-core/src/main/java/hudson/util/ProcessTree.java

http://github.com/hudson/hudson
Java | 1197 lines | 689 code | 153 blank | 355 comment | 63 complexity | 3fe5ad618713733198abedba8609b9de MD5 | raw file
   1/*
   2 * The MIT License
   3 *
   4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
   5 *
   6 * Permission is hereby granted, free of charge, to any person obtaining a copy
   7 * of this software and associated documentation files (the "Software"), to deal
   8 * in the Software without restriction, including without limitation the rights
   9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10 * copies of the Software, and to permit persons to whom the Software is
  11 * furnished to do so, subject to the following conditions:
  12 *
  13 * The above copyright notice and this permission notice shall be included in
  14 * all copies or substantial portions of the Software.
  15 *
  16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22 * THE SOFTWARE.
  23 */
  24package hudson.util;
  25
  26import com.sun.jna.Memory;
  27import com.sun.jna.Native;
  28import static com.sun.jna.Pointer.NULL;
  29import com.sun.jna.ptr.IntByReference;
  30import hudson.EnvVars;
  31import hudson.Util;
  32import hudson.model.Hudson;
  33import hudson.remoting.Callable;
  34import hudson.remoting.Channel;
  35import hudson.remoting.VirtualChannel;
  36import hudson.slaves.SlaveComputer;
  37import hudson.util.ProcessTree.OSProcess;
  38import static hudson.util.jna.GNUCLibrary.LIBC;
  39
  40import hudson.util.ProcessTreeRemoting.IOSProcess;
  41import hudson.util.ProcessTreeRemoting.IProcessTree;
  42import org.apache.commons.io.FileUtils;
  43import org.jvnet.winp.WinProcess;
  44import org.jvnet.winp.WinpException;
  45
  46import java.io.BufferedReader;
  47import java.io.ByteArrayOutputStream;
  48import java.io.DataInputStream;
  49import java.io.File;
  50import java.io.FileFilter;
  51import java.io.FileReader;
  52import java.io.IOException;
  53import java.io.RandomAccessFile;
  54import java.io.Serializable;
  55import java.lang.reflect.Field;
  56import java.lang.reflect.InvocationTargetException;
  57import java.lang.reflect.Method;
  58import java.util.ArrayList;
  59import java.util.Collections;
  60import java.util.HashMap;
  61import java.util.Iterator;
  62import java.util.List;
  63import java.util.Locale;
  64import java.util.Map;
  65import java.util.Map.Entry;
  66import java.util.SortedMap;
  67import java.util.Arrays;
  68import java.util.logging.Level;
  69import static java.util.logging.Level.FINER;
  70import static java.util.logging.Level.FINEST;
  71import java.util.logging.Logger;
  72
  73/**
  74 * Represents a snapshot of the process tree of the current system.
  75 *
  76 * <p>
  77 * A {@link ProcessTree} is really conceptually a map from process ID to a {@link OSProcess} object.
  78 * When Hudson runs on platforms that support process introspection, this allows you to introspect
  79 * and do some useful things on processes. On other platforms, the implementation falls back to
  80 * "do nothing" behavior.
  81 *
  82 * <p>
  83 * {@link ProcessTree} is remotable.
  84 *
  85 * @author Kohsuke Kawaguchi
  86 * @since 1.315
  87 */
  88public abstract class ProcessTree implements Iterable<OSProcess>, IProcessTree, Serializable {
  89    /**
  90     * To be filled in the constructor of the derived type.
  91     */
  92    protected final Map<Integer/*pid*/, OSProcess> processes = new HashMap<Integer, OSProcess>();
  93
  94    /**
  95     * Lazily obtained {@link ProcessKiller}s to be applied on this process tree.
  96     */
  97    private transient volatile List<ProcessKiller> killers;
  98
  99    // instantiation only allowed for subtypes in this class
 100    private ProcessTree() {}
 101
 102    /**
 103     * Gets the process given a specific ID, or null if no such process exists.
 104     */
 105    public final OSProcess get(int pid) {
 106        return processes.get(pid);
 107    }
 108
 109    /**
 110     * Lists all the processes in the system.
 111     */
 112    public final Iterator<OSProcess> iterator() {
 113        return processes.values().iterator();
 114    }
 115
 116    /**
 117     * Try to convert {@link Process} into this process object
 118     * or null if it fails (for example, maybe the snapshot is taken after
 119     * this process has already finished.)
 120     */
 121    public abstract OSProcess get(Process proc);
 122
 123    /**
 124     * Kills all the processes that have matching environment variables.
 125     *
 126     * <p>
 127     * In this method, the method is given a
 128     * "model environment variables", which is a list of environment variables
 129     * and their values that are characteristic to the launched process.
 130     * The implementation is expected to find processes
 131     * in the system that inherit these environment variables, and kill
 132     * them all. This is suitable for locating daemon processes
 133     * that cannot be tracked by the regular ancestor/descendant relationship.
 134     */
 135    public abstract void killAll(Map<String, String> modelEnvVars) throws InterruptedException;
 136
 137    /**
 138     * Convenience method that does {@link #killAll(Map)} and {@link OSProcess#killRecursively()}.
 139     * This is necessary to reliably kill the process and its descendants, as some OS
 140     * may not implement {@link #killAll(Map)}.
 141     *
 142     * Either of the parameter can be null.
 143     */
 144    public void killAll(Process proc, Map<String, String> modelEnvVars) throws InterruptedException {
 145        LOGGER.fine("killAll: process="+proc+" and envs="+modelEnvVars);
 146        OSProcess p = get(proc);
 147        if(p!=null) p.killRecursively();
 148        if(modelEnvVars!=null)
 149            killAll(modelEnvVars);
 150    }
 151
 152    /**
 153     * Obtains the list of killers.
 154     */
 155    /*package*/ final List<ProcessKiller> getKillers() throws InterruptedException {
 156        if (killers==null)
 157            try {
 158                killers = SlaveComputer.getChannelToMaster().call(new Callable<List<ProcessKiller>, IOException>() {
 159                    public List<ProcessKiller> call() throws IOException {
 160                        return new ArrayList<ProcessKiller>(ProcessKiller.all());
 161                    }
 162                });
 163            } catch (IOException e) {
 164                LOGGER.log(Level.WARNING, "Failed to obtain killers",e);
 165                killers = Collections.emptyList();
 166            }
 167        return killers;
 168    }
 169
 170    /**
 171     * Represents a process.
 172     */
 173    public abstract class OSProcess implements IOSProcess, Serializable {
 174        final int pid;
 175
 176        // instantiation only allowed for subtypes in this class
 177        private OSProcess(int pid) {
 178            this.pid = pid;
 179        }
 180
 181        public final int getPid() {
 182            return pid;
 183        }
 184        /**
 185         * Gets the parent process. This method may return null, because
 186         * there's no guarantee that we are getting a consistent snapshot
 187         * of the whole system state.
 188         */
 189        public abstract OSProcess getParent();
 190
 191        /*package*/ final ProcessTree getTree() {
 192            return ProcessTree.this;
 193        }
 194
 195        /**
 196         * Immediate child processes.
 197         */
 198        public final List<OSProcess> getChildren() {
 199            List<OSProcess> r = new ArrayList<OSProcess>();
 200            for (OSProcess p : ProcessTree.this)
 201                if(p.getParent()==this)
 202                    r.add(p);
 203            return r;
 204        }
 205
 206        /**
 207         * Kills this process.
 208         */
 209        public abstract void kill() throws InterruptedException;
 210
 211        void killByKiller() throws InterruptedException {
 212            for (ProcessKiller killer : getKillers())
 213                try {
 214                    if (killer.kill(this))
 215                        break;
 216                } catch (IOException e) {
 217                    LOGGER.log(Level.WARNING, "Failed to kill pid="+getPid(),e);
 218                }
 219        }
 220
 221        /**
 222         * Kills this process and all the descendants.
 223         * <p>
 224         * Note that the notion of "descendants" is somewhat vague,
 225         * in the presence of such things like daemons. On platforms
 226         * where the recursive operation is not supported, this just kills
 227         * the current process. 
 228         */
 229        public abstract void killRecursively() throws InterruptedException;
 230
 231        /**
 232         * Gets the command-line arguments of this process.
 233         *
 234         * <p>
 235         * On Windows, where the OS models command-line arguments as a single string, this method
 236         * computes the approximated tokenization.
 237         */
 238        public abstract List<String> getArguments();
 239
 240        /**
 241         * Obtains the environment variables of this process.
 242         *
 243         * @return
 244         *      empty map if failed (for example because the process is already dead,
 245         *      or the permission was denied.)
 246         */
 247        public abstract EnvVars getEnvironmentVariables();
 248
 249        /**
 250         * Given the environment variable of a process and the "model environment variable" that Hudson
 251         * used for launching the build, returns true if there's a match (which means the process should
 252         * be considered a descendant of a build.)
 253         */
 254        public final boolean hasMatchingEnvVars(Map<String,String> modelEnvVar) {
 255            if(modelEnvVar.isEmpty())
 256                // sanity check so that we don't start rampage.
 257                return false;
 258
 259            SortedMap<String,String> envs = getEnvironmentVariables();
 260            for (Entry<String,String> e : modelEnvVar.entrySet()) {
 261                String v = envs.get(e.getKey());
 262                if(v==null || !v.equals(e.getValue()))
 263                    return false;   // no match
 264            }
 265
 266            return true;
 267        }
 268
 269        /**
 270         * Executes a chunk of code at the same machine where this process resides.
 271         */
 272        public <T> T act(ProcessCallable<T> callable) throws IOException, InterruptedException {
 273            return callable.invoke(this,Hudson.MasterComputer.localChannel);
 274        }
 275
 276        Object writeReplace() {
 277            return new SerializedProcess(pid);
 278        }
 279    }
 280
 281    /**
 282     * Serialized form of {@link OSProcess} is the PID and {@link ProcessTree}
 283     */
 284    private final class SerializedProcess implements Serializable {
 285        private final int pid;
 286        private static final long serialVersionUID = 1L;
 287
 288        private SerializedProcess(int pid) {
 289            this.pid = pid;
 290        }
 291
 292        Object readResolve() {
 293            return get(pid);
 294        }
 295    }
 296
 297    /**
 298     * Code that gets executed on the machine where the {@link OSProcess} is local.
 299     * Used to act on {@link OSProcess}.
 300     *
 301     * @see OSProcess#act(ProcessCallable)
 302     */
 303    public static interface ProcessCallable<T> extends Serializable {
 304        /**
 305         * Performs the computational task on the node where the data is located.
 306         *
 307         * @param process
 308         *      {@link OSProcess} that represents the local process.
 309         * @param channel
 310         *      The "back pointer" of the {@link Channel} that represents the communication
 311         *      with the node from where the code was sent.
 312         */
 313        T invoke(OSProcess process, VirtualChannel channel) throws IOException;
 314    }
 315
 316
 317    /**
 318     * Gets the {@link ProcessTree} of the current system
 319     * that JVM runs in, or in the worst case return the default one
 320     * that's not capable of killing descendants at all.
 321     */
 322    public static ProcessTree get() {
 323        if(!enabled)
 324            return DEFAULT;
 325
 326        try {
 327            if(File.pathSeparatorChar==';')
 328                return new Windows();
 329
 330            String os = Util.fixNull(System.getProperty("os.name"));
 331            if(os.equals("Linux"))
 332                return new Linux();
 333            if(os.equals("SunOS"))
 334                return new Solaris();
 335            if(os.equals("Mac OS X"))
 336                return new Darwin();
 337        } catch (LinkageError e) {
 338            LOGGER.log(Level.WARNING,"Failed to load winp. Reverting to the default",e);
 339            enabled = false;
 340        }
 341
 342        return DEFAULT;
 343    }
 344
 345//
 346//
 347// implementation follows
 348//-------------------------------------------
 349//
 350
 351    /**
 352     * Empty process list as a default value if the platform doesn't support it.
 353     */
 354    private static final ProcessTree DEFAULT = new Local() {
 355        public OSProcess get(final Process proc) {
 356            return new OSProcess(-1) {
 357                public OSProcess getParent() {
 358                    return null;
 359                }
 360
 361                public void killRecursively() {
 362                    // fall back to a single process killer
 363                    proc.destroy();
 364                }
 365
 366                public void kill() throws InterruptedException {
 367                    proc.destroy();
 368                    killByKiller();
 369                }
 370
 371                public List<String> getArguments() {
 372                    return Collections.emptyList();
 373                }
 374
 375                public EnvVars getEnvironmentVariables() {
 376                    return new EnvVars();
 377                }
 378            };
 379        }
 380
 381        public void killAll(Map<String, String> modelEnvVars) {
 382            // no-op
 383        }
 384    };
 385
 386
 387    private static final class Windows extends Local {
 388        Windows() {
 389            for (final WinProcess p : WinProcess.all()) {
 390                int pid = p.getPid();
 391                super.processes.put(pid,new OSProcess(pid) {
 392                    private EnvVars env;
 393                    private List<String> args;
 394
 395                    public OSProcess getParent() {
 396                        // windows process doesn't have parent/child relationship
 397                        return null;
 398                    }
 399
 400                    public void killRecursively() {
 401                        LOGGER.finer("Killing recursively "+getPid());
 402                        p.killRecursively();
 403                    }
 404
 405                    public void kill() throws InterruptedException {
 406                        LOGGER.finer("Killing "+getPid());
 407                        p.kill();
 408                        killByKiller();
 409                    }
 410
 411                    @Override
 412                    public synchronized List<String> getArguments() {
 413                        if(args==null)  args = Arrays.asList(QuotedStringTokenizer.tokenize(p.getCommandLine()));
 414                        return args;
 415                    }
 416
 417                    @Override
 418                    public synchronized EnvVars getEnvironmentVariables() {
 419                        if(env==null)   env = new EnvVars(p.getEnvironmentVariables());
 420                        return env;
 421                    }
 422                });
 423
 424            }
 425        }
 426
 427        @Override
 428        public OSProcess get(Process proc) {
 429            return get(new WinProcess(proc).getPid());
 430        }
 431
 432        public void killAll(Map<String, String> modelEnvVars) throws InterruptedException {
 433            for( OSProcess p : this) {
 434                if(p.getPid()<10)
 435                    continue;   // ignore system processes like "idle process"
 436
 437                LOGGER.finest("Considering to kill "+p.getPid());
 438
 439                boolean matched;
 440                try {
 441                    matched = p.hasMatchingEnvVars(modelEnvVars);
 442                } catch (WinpException e) {
 443                    // likely a missing privilege
 444                    LOGGER.log(FINEST,"  Failed to check environment variable match",e);
 445                    continue;
 446                }
 447
 448                if(matched)
 449                    p.killRecursively();
 450                else
 451                    LOGGER.finest("Environment variable didn't match");
 452
 453            }
 454        }
 455
 456        static {
 457            WinProcess.enableDebugPrivilege();
 458        }
 459    }
 460
 461    static abstract class Unix extends Local {
 462        @Override
 463        public OSProcess get(Process proc) {
 464            try {
 465                return get((Integer) UnixReflection.PID_FIELD.get(proc));
 466            } catch (IllegalAccessException e) { // impossible
 467                IllegalAccessError x = new IllegalAccessError();
 468                x.initCause(e);
 469                throw x;
 470            }
 471        }
 472
 473        public void killAll(Map<String, String> modelEnvVars) throws InterruptedException {
 474            for (OSProcess p : this)
 475                if(p.hasMatchingEnvVars(modelEnvVars))
 476                    p.killRecursively();
 477        }
 478    }
 479    /**
 480     * {@link ProcessTree} based on /proc.
 481     */
 482    static abstract class ProcfsUnix extends Unix {
 483        ProcfsUnix() {
 484            File[] processes = new File("/proc").listFiles(new FileFilter() {
 485                public boolean accept(File f) {
 486                    return f.isDirectory();
 487                }
 488            });
 489            if(processes==null) {
 490                LOGGER.info("No /proc");
 491                return;
 492            }
 493
 494            for (File p : processes) {
 495                int pid;
 496                try {
 497                    pid = Integer.parseInt(p.getName());
 498                } catch (NumberFormatException e) {
 499                    // other sub-directories
 500                    continue;
 501                }
 502                try {
 503                    this.processes.put(pid,createProcess(pid));
 504                } catch (IOException e) {
 505                    // perhaps the process status has changed since we obtained a directory listing
 506                }
 507            }
 508        }
 509
 510        protected abstract OSProcess createProcess(int pid) throws IOException;
 511    }
 512
 513    /**
 514     * A process.
 515     */
 516    public abstract class UnixProcess extends OSProcess {
 517        protected UnixProcess(int pid) {
 518            super(pid);
 519        }
 520
 521        protected final File getFile(String relativePath) {
 522            return new File(new File("/proc/"+getPid()),relativePath);
 523        }
 524
 525        /**
 526         * Tries to kill this process.
 527         */
 528        public void kill() throws InterruptedException {
 529            try {
 530                int pid = getPid();
 531                LOGGER.fine("Killing pid="+pid);
 532                UnixReflection.DESTROY_PROCESS.invoke(null, pid);
 533            } catch (IllegalAccessException e) {
 534                // this is impossible
 535                IllegalAccessError x = new IllegalAccessError();
 536                x.initCause(e);
 537                throw x;
 538            } catch (InvocationTargetException e) {
 539                // tunnel serious errors
 540                if(e.getTargetException() instanceof Error)
 541                    throw (Error)e.getTargetException();
 542                // otherwise log and let go. I need to see when this happens
 543                LOGGER.log(Level.INFO, "Failed to terminate pid="+getPid(),e);
 544            }
 545            killByKiller();
 546        }
 547
 548        public void killRecursively() throws InterruptedException {
 549            LOGGER.fine("Recursively killing pid="+getPid());
 550            for (OSProcess p : getChildren())
 551                p.killRecursively();
 552            kill();
 553        }
 554
 555        /**
 556         * Obtains the argument list of this process.
 557         *
 558         * @return
 559         *      empty list if failed (for example because the process is already dead,
 560         *      or the permission was denied.)
 561         */
 562        public abstract List<String> getArguments();
 563    }
 564
 565    /**
 566     * Reflection used in the Unix support.
 567     */
 568    private static final class UnixReflection {
 569        /**
 570         * Field to access the PID of the process.
 571         */
 572        private static final Field PID_FIELD;
 573
 574        /**
 575         * Method to destroy a process, given pid.
 576         */
 577        private static final Method DESTROY_PROCESS;
 578
 579        static {
 580            try {
 581                Class<?> clazz = Class.forName("java.lang.UNIXProcess");
 582                PID_FIELD = clazz.getDeclaredField("pid");
 583                PID_FIELD.setAccessible(true);
 584
 585                DESTROY_PROCESS = clazz.getDeclaredMethod("destroyProcess",int.class);
 586                DESTROY_PROCESS.setAccessible(true);
 587            } catch (ClassNotFoundException e) {
 588                LinkageError x = new LinkageError();
 589                x.initCause(e);
 590                throw x;
 591            } catch (NoSuchFieldException e) {
 592                LinkageError x = new LinkageError();
 593                x.initCause(e);
 594                throw x;
 595            } catch (NoSuchMethodException e) {
 596                LinkageError x = new LinkageError();
 597                x.initCause(e);
 598                throw x;
 599            }
 600        }
 601    }
 602
 603
 604    static class Linux extends ProcfsUnix {
 605        protected LinuxProcess createProcess(int pid) throws IOException {
 606            return new LinuxProcess(pid);
 607        }
 608
 609        class LinuxProcess extends UnixProcess {
 610            private int ppid = -1;
 611            private EnvVars envVars;
 612            private List<String> arguments;
 613
 614            LinuxProcess(int pid) throws IOException {
 615                super(pid);
 616
 617                BufferedReader r = new BufferedReader(new FileReader(getFile("status")));
 618                try {
 619                    String line;
 620                    while((line=r.readLine())!=null) {
 621                        line=line.toLowerCase(Locale.ENGLISH);
 622                        if(line.startsWith("ppid:")) {
 623                            ppid = Integer.parseInt(line.substring(5).trim());
 624                            break;
 625                        }
 626                    }
 627                } finally {
 628                    r.close();
 629                }
 630                if(ppid==-1)
 631                    throw new IOException("Failed to parse PPID from /proc/"+pid+"/status");
 632            }
 633
 634            public OSProcess getParent() {
 635                return get(ppid);
 636            }
 637
 638            public synchronized List<String> getArguments() {
 639                if(arguments!=null)
 640                    return arguments;
 641                arguments = new ArrayList<String>();
 642                try {
 643                    byte[] cmdline = FileUtils.readFileToByteArray(getFile("cmdline"));
 644                    int pos=0;
 645                    for (int i = 0; i < cmdline.length; i++) {
 646                        byte b = cmdline[i];
 647                        if(b==0) {
 648                            arguments.add(new String(cmdline,pos,i-pos));
 649                            pos=i+1;
 650                        }
 651                    }
 652                } catch (IOException e) {
 653                    // failed to read. this can happen under normal circumstances (most notably permission denied)
 654                    // so don't report this as an error.
 655                }
 656                arguments = Collections.unmodifiableList(arguments);
 657                return arguments;
 658            }
 659
 660            public synchronized EnvVars getEnvironmentVariables() {
 661                if(envVars !=null)
 662                    return envVars;
 663                envVars = new EnvVars();
 664                try {
 665                    byte[] environ = FileUtils.readFileToByteArray(getFile("environ"));
 666                    int pos=0;
 667                    for (int i = 0; i < environ.length; i++) {
 668                        byte b = environ[i];
 669                        if(b==0) {
 670                            envVars.addLine(new String(environ,pos,i-pos));
 671                            pos=i+1;
 672                        }
 673                    }
 674                } catch (IOException e) {
 675                    // failed to read. this can happen under normal circumstances (most notably permission denied)
 676                    // so don't report this as an error.
 677                }
 678                return envVars;
 679            }
 680        }
 681    }
 682
 683    /**
 684     * Implementation for Solaris that uses <tt>/proc</tt>.
 685     *
 686     * Amazingly, this single code works for both 32bit and 64bit Solaris, despite the fact
 687     * that does a lot of pointer manipulation and what not.
 688     */
 689    static class Solaris extends ProcfsUnix {
 690        protected OSProcess createProcess(final int pid) throws IOException {
 691            return new SolarisProcess(pid);
 692        }
 693
 694        private class SolarisProcess extends UnixProcess {
 695            private final int ppid;
 696            /**
 697                 * Address of the environment vector. Even on 64bit Solaris this is still 32bit pointer.
 698             */
 699            private final int envp;
 700            /**
 701                 * Similarly, address of the arguments vector.
 702             */
 703            private final int argp;
 704            private final int argc;
 705            private EnvVars envVars;
 706            private List<String> arguments;
 707
 708            private SolarisProcess(int pid) throws IOException {
 709                super(pid);
 710
 711                RandomAccessFile psinfo = new RandomAccessFile(getFile("psinfo"),"r");
 712                try {
 713                    // see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/procfs.h
 714                    //typedef struct psinfo {
 715                    //	int	pr_flag;	/* process flags */
 716                    //	int	pr_nlwp;	/* number of lwps in the process */
 717                    //	pid_t	pr_pid;	/* process id */
 718                    //	pid_t	pr_ppid;	/* process id of parent */
 719                    //	pid_t	pr_pgid;	/* process id of process group leader */
 720                    //	pid_t	pr_sid;	/* session id */
 721                    //	uid_t	pr_uid;	/* real user id */
 722                    //	uid_t	pr_euid;	/* effective user id */
 723                    //	gid_t	pr_gid;	/* real group id */
 724                    //	gid_t	pr_egid;	/* effective group id */
 725                    //	uintptr_t	pr_addr;	/* address of process */
 726                    //	size_t	pr_size;	/* size of process image in Kbytes */
 727                    //	size_t	pr_rssize;	/* resident set size in Kbytes */
 728                    //	dev_t	pr_ttydev;	/* controlling tty device (or PRNODEV) */
 729                    //	ushort_t	pr_pctcpu;	/* % of recent cpu time used by all lwps */
 730                    //	ushort_t	pr_pctmem;	/* % of system memory used by process */
 731                    //	timestruc_t	pr_start;	/* process start time, from the epoch */
 732                    //	timestruc_t	pr_time;	/* cpu time for this process */
 733                    //	timestruc_t	pr_ctime;	/* cpu time for reaped children */
 734                    //	char	pr_fname[PRFNSZ];	/* name of exec'ed file */
 735                    //	char	pr_psargs[PRARGSZ];	/* initial characters of arg list */
 736                    //	int	pr_wstat;	/* if zombie, the wait() status */
 737                    //	int	pr_argc;	/* initial argument count */
 738                    //	uintptr_t	pr_argv;	/* address of initial argument vector */
 739                    //	uintptr_t	pr_envp;	/* address of initial environment vector */
 740                    //	char	pr_dmodel;	/* data model of the process */
 741                    //	lwpsinfo_t	pr_lwp;	/* information for representative lwp */
 742                    //} psinfo_t;
 743
 744                    // see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/types.h
 745                    // for the size of the various datatype.
 746
 747                    // see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/cmd/ptools/pargs/pargs.c
 748                    // for how to read this information
 749
 750                    psinfo.seek(8);
 751                    if(adjust(psinfo.readInt())!=pid)
 752                        throw new IOException("psinfo PID mismatch");   // sanity check
 753                    ppid = adjust(psinfo.readInt());
 754
 755                    psinfo.seek(188);  // now jump to pr_argc
 756                    argc = adjust(psinfo.readInt());
 757                    argp = adjust(psinfo.readInt());
 758                    envp = adjust(psinfo.readInt());
 759                } finally {
 760                    psinfo.close();
 761                }
 762                if(ppid==-1)
 763                    throw new IOException("Failed to parse PPID from /proc/"+pid+"/status");
 764
 765            }
 766
 767            public OSProcess getParent() {
 768                return get(ppid);
 769            }
 770
 771            public synchronized List<String> getArguments() {
 772                if(arguments!=null)
 773                    return arguments;
 774
 775                arguments = new ArrayList<String>(argc);
 776
 777                try {
 778                    RandomAccessFile as = new RandomAccessFile(getFile("as"),"r");
 779                    if(LOGGER.isLoggable(FINER))
 780                        LOGGER.finer("Reading "+getFile("as"));
 781                    try {
 782                        for( int n=0; n<argc; n++ ) {
 783                            // read a pointer to one entry
 784                            as.seek(to64(argp+n*4));
 785                            int p = adjust(as.readInt());
 786
 787                            arguments.add(readLine(as, p, "argv["+ n +"]"));
 788                        }
 789                    } finally {
 790                        as.close();
 791                    }
 792                } catch (IOException e) {
 793                    // failed to read. this can happen under normal circumstances (most notably permission denied)
 794                    // so don't report this as an error.
 795                }
 796
 797                arguments = Collections.unmodifiableList(arguments);
 798                return arguments;
 799            }
 800
 801            public synchronized EnvVars getEnvironmentVariables() {
 802                if(envVars !=null)
 803                    return envVars;
 804                envVars = new EnvVars();
 805
 806                try {
 807                    RandomAccessFile as = new RandomAccessFile(getFile("as"),"r");
 808                    if(LOGGER.isLoggable(FINER))
 809                        LOGGER.finer("Reading "+getFile("as"));
 810                    try {
 811                        for( int n=0; ; n++ ) {
 812                            // read a pointer to one entry
 813                            as.seek(to64(envp+n*4));
 814                            int p = adjust(as.readInt());
 815                            if(p==0)
 816                                break;  // completed the walk
 817
 818                            // now read the null-terminated string
 819                            envVars.addLine(readLine(as, p, "env["+ n +"]"));
 820                        }
 821                    } finally {
 822                        as.close();
 823                    }
 824                } catch (IOException e) {
 825                    // failed to read. this can happen under normal circumstances (most notably permission denied)
 826                    // so don't report this as an error.
 827                }
 828
 829                return envVars;
 830            }
 831
 832            private String readLine(RandomAccessFile as, int p, String prefix) throws IOException {
 833                if(LOGGER.isLoggable(FINEST))
 834                    LOGGER.finest("Reading "+prefix+" at "+p);
 835
 836                as.seek(to64(p));
 837                ByteArrayOutputStream buf = new ByteArrayOutputStream();
 838                int ch,i=0;
 839                while((ch=as.read())>0) {
 840                    if((++i)%100==0 && LOGGER.isLoggable(FINEST))
 841                        LOGGER.finest(prefix +" is so far "+buf.toString());
 842
 843                    buf.write(ch);
 844                }
 845                String line = buf.toString();
 846                if(LOGGER.isLoggable(FINEST))
 847                    LOGGER.finest(prefix+" was "+line);
 848                return line;
 849            }
 850        }
 851
 852        /**
 853         * int to long conversion with zero-padding.
 854         */
 855        private static long to64(int i) {
 856            return i&0xFFFFFFFFL;
 857        }
 858
 859        /**
 860         * {@link DataInputStream} reads a value in big-endian, so
 861         * convert it to the correct value on little-endian systems.
 862         */
 863        private static int adjust(int i) {
 864            if(IS_LITTLE_ENDIAN)
 865                return (i<<24) |((i<<8) & 0x00FF0000) | ((i>>8) & 0x0000FF00) | (i>>>24);
 866            else
 867                return i;
 868        }
 869
 870    }
 871
 872    /**
 873     * Implementation for Mac OS X based on sysctl(3).
 874     */
 875    private static class Darwin extends Unix {
 876        Darwin() {
 877            try {
 878                IntByReference _ = new IntByReference(sizeOfInt);
 879                IntByReference size = new IntByReference(sizeOfInt);
 880                Memory m;
 881                int nRetry = 0;
 882                while(true) {
 883                    // find out how much memory we need to do this
 884                    if(LIBC.sysctl(MIB_PROC_ALL,3, NULL, size, NULL, _)!=0)
 885                        throw new IOException("Failed to obtain memory requirement: "+LIBC.strerror(Native.getLastError()));
 886
 887                    // now try the real call
 888                    m = new Memory(size.getValue());
 889                    if(LIBC.sysctl(MIB_PROC_ALL,3, m, size, NULL, _)!=0) {
 890                        if(Native.getLastError()==ENOMEM && nRetry++<16)
 891                            continue; // retry
 892                        throw new IOException("Failed to call kern.proc.all: "+LIBC.strerror(Native.getLastError()));
 893                    }
 894                    break;
 895                }
 896
 897                int count = size.getValue()/sizeOf_kinfo_proc;
 898                LOGGER.fine("Found "+count+" processes");
 899
 900                for( int base=0; base<size.getValue(); base+=sizeOf_kinfo_proc) {
 901                    int pid = m.getInt(base+24);
 902                    int ppid = m.getInt(base+416);
 903//                    int effective_uid = m.getInt(base+304);
 904//                    byte[] comm = new byte[16];
 905//                    m.read(base+163,comm,0,16);
 906
 907                    super.processes.put(pid,new DarwinProcess(pid,ppid));
 908                }
 909            } catch (IOException e) {
 910                LOGGER.log(Level.WARNING, "Failed to obtain process list",e);
 911            }
 912        }
 913
 914        private class DarwinProcess extends UnixProcess {
 915            private final int ppid;
 916            private EnvVars envVars;
 917            private List<String> arguments;
 918
 919            DarwinProcess(int pid, int ppid) {
 920                super(pid);
 921                this.ppid = ppid;
 922            }
 923
 924            public OSProcess getParent() {
 925                return get(ppid);
 926            }
 927
 928            public synchronized EnvVars getEnvironmentVariables() {
 929                if(envVars !=null)
 930                    return envVars;
 931                parse();
 932                return envVars;
 933            }
 934
 935            public List<String> getArguments() {
 936                if(arguments !=null)
 937                    return arguments;
 938                parse();
 939                return arguments;
 940            }
 941
 942            private void parse() {
 943                try {
 944// allocate them first, so that the parse error wil result in empty data
 945                    // and avoid retry.
 946                    arguments = new ArrayList<String>();
 947                    envVars = new EnvVars();
 948
 949                    IntByReference _ = new IntByReference();
 950
 951                    IntByReference argmaxRef = new IntByReference(0);
 952                    IntByReference size = new IntByReference(sizeOfInt);
 953
 954                    // for some reason, I was never able to get sysctlbyname work.
 955//        if(LIBC.sysctlbyname("kern.argmax", argmaxRef.getPointer(), size, NULL, _)!=0)
 956                    if(LIBC.sysctl(new int[]{CTL_KERN,KERN_ARGMAX},2, argmaxRef.getPointer(), size, NULL, _)!=0)
 957                        throw new IOException("Failed to get kernl.argmax: "+LIBC.strerror(Native.getLastError()));
 958
 959                    int argmax = argmaxRef.getValue();
 960
 961                    class StringArrayMemory extends Memory {
 962                        private long offset=0;
 963
 964                        StringArrayMemory(long l) {
 965                            super(l);
 966                        }
 967
 968                        int readInt() {
 969                            int r = getInt(offset);
 970                            offset+=sizeOfInt;
 971                            return r;
 972                        }
 973
 974                        byte peek() {
 975                            return getByte(offset);
 976                        }
 977
 978                        String readString() {
 979                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
 980                            byte ch;
 981                            while((ch = getByte(offset++))!='\0')
 982                                baos.write(ch);
 983                            return baos.toString();
 984                        }
 985
 986                        void skip0() {
 987                            // skip trailing '\0's
 988                            while(getByte(offset)=='\0')
 989                                offset++;
 990                        }
 991                    }
 992                    StringArrayMemory m = new StringArrayMemory(argmax);
 993                    size.setValue(argmax);
 994                    if(LIBC.sysctl(new int[]{CTL_KERN,KERN_PROCARGS2,pid},3, m, size, NULL, _)!=0)
 995                        throw new IOException("Failed to obtain ken.procargs2: "+LIBC.strerror(Native.getLastError()));
 996
 997
 998                    /*
 999                    * Make a sysctl() call to get the raw argument space of the
1000                        * process.  The layout is documented in start.s, which is part
1001                        * of the Csu project.  In summary, it looks like:
1002                        *
1003                        * /---------------\ 0x00000000
1004                        * :               :
1005                        * :               :
1006                        * |---------------|
1007                        * | argc          |
1008                        * |---------------|
1009                        * | arg[0]        |
1010                        * |---------------|
1011                        * :               :
1012                        * :               :
1013                        * |---------------|
1014                        * | arg[argc - 1] |
1015                        * |---------------|
1016                        * | 0             |
1017                        * |---------------|
1018                        * | env[0]        |
1019                        * |---------------|
1020                        * :               :
1021                        * :               :
1022                        * |---------------|
1023                        * | env[n]        |
1024                        * |---------------|
1025                        * | 0             |
1026                        * |---------------| <-- Beginning of data returned by sysctl()
1027                        * | exec_path     |     is here.
1028                        * |:::::::::::::::|
1029                        * |               |
1030                        * | String area.  |
1031                        * |               |
1032                        * |---------------| <-- Top of stack.
1033                        * :               :
1034                        * :               :
1035                        * \---------------/ 0xffffffff
1036                        */
1037
1038                    int nargs = m.readInt();
1039                    m.readString(); // exec path
1040                    for( int i=0; i<nargs; i++) {
1041                        m.skip0();
1042                        arguments.add(m.readString());
1043                    }
1044
1045                    // this is how you can read environment variables
1046                    while(m.peek()!=0)
1047                    envVars.addLine(m.readString());
1048                } catch (IOException e) {
1049                    // this happens with insufficient permissions, so just ignore the problem.
1050                }
1051            }
1052        }
1053
1054        // local constants
1055        private static final int sizeOf_kinfo_proc = 492; // TODO:checked on 32bit Mac OS X. is this different on 64bit?
1056        private static final int sizeOfInt = Native.getNativeSize(int.class);
1057        private static final int CTL_KERN = 1;
1058        private static final int KERN_PROC = 14;
1059        private static final int KERN_PROC_ALL = 0;
1060        private static final int ENOMEM = 12;
1061        private static int[] MIB_PROC_ALL = {CTL_KERN, KERN_PROC, KERN_PROC_ALL};
1062        private static final int KERN_ARGMAX = 8;
1063        private static final int KERN_PROCARGS2 = 49;
1064    }
1065
1066    /**
1067     * Represents a local process tree, where this JVM and the process tree run on the same system.
1068     * (The opposite of {@link Remote}.)
1069     */
1070    public static abstract class Local extends ProcessTree {
1071        Local() {
1072        }
1073    }
1074
1075    /**
1076     * Represents a process tree over a channel.
1077     */
1078    public static class Remote extends ProcessTree implements Serializable {
1079        private final IProcessTree proxy;
1080
1081        public Remote(ProcessTree proxy, Channel ch) {
1082            this.proxy = ch.export(IProcessTree.class,proxy);
1083            for (Entry<Integer,OSProcess> e : proxy.processes.entrySet())
1084                processes.put(e.getKey(),new RemoteProcess(e.getValue(),ch));
1085        }
1086
1087        @Override
1088        public OSProcess get(Process proc) {
1089            return null;
1090        }
1091
1092        @Override
1093        public void killAll(Map<String, String> modelEnvVars) throws InterruptedException {
1094            proxy.killAll(modelEnvVars);
1095        }
1096
1097        Object writeReplace() {
1098            return this; // cancel out super.writeReplace()
1099        }
1100        
1101        private static final long serialVersionUID = 1L;
1102
1103        private class RemoteProcess extends OSProcess implements Serializable {
1104            private final IOSProcess proxy;
1105
1106            RemoteProcess(OSProcess proxy, Channel ch) {
1107                super(proxy.getPid());
1108                this.proxy = ch.export(IOSProcess.class,proxy);
1109            }
1110
1111            public OSProcess getParent() {
1112                IOSProcess p = proxy.getParent();
1113                if (p==null)    return null;
1114                return get(p.getPid());
1115            }
1116
1117            public void kill() throws InterruptedException {
1118                proxy.kill();
1119            }
1120
1121            public void killRecursively() throws InterruptedException {
1122                proxy.killRecursively();
1123            }
1124
1125            public List<String> getArguments() {
1126                return proxy.getArguments();
1127            }
1128
1129            public EnvVars getEnvironmentVariables() {
1130                return proxy.getEnvironmentVariables();
1131            }
1132
1133            Object writeReplace() {
1134                return this; // cancel out super.writeReplace()
1135            }
1136
1137            public <T> T act(ProcessCallable<T> callable) throws IOException, InterruptedException {
1138                return proxy.act(callable);
1139            }
1140
1141
1142            private static final long serialVersionUID = 1L;
1143        }
1144    }
1145
1146    /**
1147     * Use {@link Remote} as the serialized form.
1148     */
1149    /*package*/ Object writeReplace() {
1150        return new Remote(this,Channel.current());
1151    }
1152
1153//    public static void main(String[] args) {
1154//        // dump everything
1155//        LOGGER.setLevel(Level.ALL);
1156//        ConsoleHandler h = new ConsoleHandler();
1157//        h.setLevel(Level.ALL);
1158//        LOGGER.addHandler(h);
1159//
1160//        Solaris killer = (Solaris)get();
1161//        Solaris.SolarisSystem s = killer.createSystem();
1162//        Solaris.SolarisProcess p = s.get(Integer.parseInt(args[0]));
1163//        System.out.println(p.getEnvVars());
1164//
1165//        if(args.length==2)
1166//            p.kill();
1167//    }
1168
1169    /*
1170        On MacOS X, there's no procfs <http://www.osxbook.com/book/bonus/chapter11/procfs/>
1171        instead you'd do it with the sysctl <http://search.cpan.org/src/DURIST/Proc-ProcessTable-0.42/os/darwin.c>
1172        <http://developer.apple.com/documentation/Darwin/Reference/ManPages/man3/sysctl.3.html>
1173
1174        There's CLI but that doesn't seem to offer the access to per-process info
1175        <http://developer.apple.com/documentation/Darwin/Reference/ManPages/man8/sysctl.8.html>
1176
1177
1178
1179        On HP-UX, pstat_getcommandline get you command line, but I'm not seeing any environment variables.
1180     */
1181
1182    private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian"));
1183    private static final Logger LOGGER = Logger.getLogger(ProcessTree.class.getName());
1184
1185    /**
1186     * Flag to control this feature.
1187     *
1188     * <p>
1189     * This feature involves some native code, so we are allowing the user to disable this
1190     * in case there's a fatal problem.
1191     *
1192     * <p>
1193     * This property supports two names for a compatibility reason.
1194     */
1195    public static boolean enabled = !Boolean.getBoolean(ProcessTreeKiller.class.getName()+".disable")
1196                                 && !Boolean.getBoolean(ProcessTree.class.getName()+".disable");
1197}