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