File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\util\ProcessTree.java.html

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