1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  *
26  * ident	"%Z%%M%	%I%	%E% SMI"
27  */
28 package org.opensolaris.os.dtrace;
29 
30 import java.io.*;
31 import java.beans.*;
32 
33 /**
34  * State of a target process designated by {@link
35  * Consumer#createProcess(String command)} or {@link
36  * Consumer#grabProcess(int pid)}.
37  * <p>
38  * Immutable.  Supports persistence using {@link java.beans.XMLEncoder}.
39  *
40  * @see ConsumerListener#processStateChanged(ProcessEvent e)
41  *
42  * @author Tom Erickson
43  */
44 public final class ProcessState implements Serializable {
45     static final long serialVersionUID = -3395911213431317292L;
46 
47     static {
48 	try {
49 	    BeanInfo info = Introspector.getBeanInfo(ProcessState.class);
50 	    PersistenceDelegate persistenceDelegate =
51 		    new DefaultPersistenceDelegate(
52 		    new String[] {"processID", "state",
53 		    "terminationSignal", "terminationSignalName",
54 		    "exitStatus", "message"})
55 	    {
56 		/*
57 		 * Need to prevent DefaultPersistenceDelegate from using
58 		 * overridden equals() method, resulting in a
59 		 * StackOverFlowError.  Revert to PersistenceDelegate
60 		 * implementation.  See
61 		 * http://forum.java.sun.com/thread.jspa?threadID=
62 		 * 477019&tstart=135
63 		 */
64 		protected boolean
65 		mutatesTo(Object oldInstance, Object newInstance)
66 		{
67 		    return (newInstance != null && oldInstance != null &&
68 			    oldInstance.getClass() == newInstance.getClass());
69 		}
70 
71 		protected Expression
72 		instantiate(Object oldInstance, Encoder out)
73 		{
74 		    ProcessState pstate = (ProcessState)oldInstance;
75 		    return new Expression(oldInstance, oldInstance.getClass(),
76 			    "new", new Object[] { pstate.getProcessID(),
77 			    pstate.getState().name(),
78 			    pstate.getTerminationSignal(),
79 			    pstate.getTerminationSignalName(),
80 			    pstate.getExitStatus(),
81 			    pstate.getMessage() });
82 		}
83 	    };
84 	    BeanDescriptor d = info.getBeanDescriptor();
85 	    d.setValue("persistenceDelegate", persistenceDelegate);
86 	} catch (IntrospectionException e) {
87 	    System.out.println(e);
88 	}
89     }
90 
91     /**
92      * State of a target process.
93      */
94     public enum State {
95 	/** Process is running. */
96 	RUN,
97 	/** Process is stopped. */
98 	STOP,
99 	/** Process is lost to control. */
100 	LOST,
101 	/** Process is terminated (zombie). */
102 	UNDEAD,
103 	/** Process is terminated (core file). */
104 	DEAD
105     }
106 
107     /** @serial */
108     private int processID;
109     /** @serial */
110     private State state;
111     /** @serial */
112     private int terminationSignal;
113     /** @serial */
114     private String terminationSignalName;
115     /** @serial */
116     private Integer exitStatus;
117     /** @serial */
118     private String message;
119 
120     /**
121      * Creates a {@code ProcessState} instance with the given state.
122      *
123      * @param pid non-negative target process ID
124      * @param processState target process state
125      * @param processTerminationSignal signal that terminated the target
126      * process, {@code -1} if the process was not terminated by a signal
127      * or if the terminating signal is unknown
128      * @param processTerminationSignalName name of the signal that
129      * terminated the target process, {@code null} if the process was
130      * not terminated by a signal or if the terminating signal is
131      * unknown
132      * @param processExitStatus target process exit status, {@code null}
133      * if the process has not exited or the exit status is unknown
134      * @param msg message included by DTrace, if any
135      * @throws NullPointerException if the given process state is {@code
136      * null}
137      * @throws IllegalArgumentException if the given process ID is negative
138      */
139     public
ProcessState(int pid, State processState, int processTerminationSignal, String processTerminationSignalName, Integer processExitStatus, String msg)140     ProcessState(int pid, State processState,
141 	    int processTerminationSignal,
142 	    String processTerminationSignalName,
143 	    Integer processExitStatus, String msg)
144     {
145 	processID = pid;
146 	state = processState;
147 	terminationSignal = processTerminationSignal;
148 	terminationSignalName = processTerminationSignalName;
149 	exitStatus = processExitStatus;
150 	message = msg;
151 	validate();
152     }
153 
154     /**
155      * Supports XML persistence.
156      *
157      * @see #ProcessState(int pid, State processState, int
158      * processTerminationSignal, String processTerminationSignalName,
159      * Integer processExitStatus, String msg)
160      * @throws IllegalArgumentException if there is no {@link
161      * ProcessState.State} value with the given state name.
162      */
163     public
ProcessState(int pid, String processStateName, int processTerminationSignal, String processTerminationSignalName, Integer processExitStatus, String msg)164     ProcessState(int pid, String processStateName,
165 	    int processTerminationSignal,
166 	    String processTerminationSignalName,
167 	    Integer processExitStatus, String msg)
168     {
169 	processID = pid;
170 	state = Enum.valueOf(State.class, processStateName);
171 	terminationSignal = processTerminationSignal;
172 	terminationSignalName = processTerminationSignalName;
173 	exitStatus = processExitStatus;
174 	message = msg;
175 	validate();
176     }
177 
178     private final void
validate()179     validate()
180     {
181 	if (processID < 0) {
182 	    throw new IllegalArgumentException("pid is negative");
183 	}
184 	if (state == null) {
185 	    throw new NullPointerException("process state is null");
186 	}
187     }
188 
189     /**
190      * Gets the process ID.
191      *
192      * @return non-negative target process ID
193      */
194     public int
getProcessID()195     getProcessID()
196     {
197 	return processID;
198     }
199 
200     /**
201      * Gets the process state.
202      *
203      * @return non-null target process state
204      */
205     public State
getState()206     getState()
207     {
208 	return state;
209     }
210 
211     /**
212      * Gets the signal that terminated the process.
213      *
214      * @return termination signal, {@code -1} if the process was not
215      * terminated by a signal or if the terminating signal is unknown
216      */
217     public int
getTerminationSignal()218     getTerminationSignal()
219     {
220 	return terminationSignal;
221     }
222 
223     /**
224      * Gets the name of the signal that terminated the process.
225      *
226      * @return termination signal name, {@code null} if the process was
227      * not terminated by a signal or if the terminating signal is
228      * unknown
229      */
230     public String
getTerminationSignalName()231     getTerminationSignalName()
232     {
233 	return terminationSignalName;
234     }
235 
236     /**
237      * Gets the process exit status.
238      *
239      * @return exit status, or {@code null} if the process has not
240      * exited or the exit status is unknown
241      */
242     public Integer
getExitStatus()243     getExitStatus()
244     {
245 	return exitStatus;
246     }
247 
248     /**
249      * Called by native code.
250      */
251     private void
setExitStatus(int status)252     setExitStatus(int status)
253     {
254 	exitStatus = new Integer(status);
255     }
256 
257     /**
258      * Gets the message from DTrace describing this process state.
259      *
260      * @return DTrace message, or {@code null} if DTrace did not include
261      * a message with this process state
262      */
263     public String
getMessage()264     getMessage()
265     {
266 	return message;
267     }
268 
269     /**
270      * Compares the specified object with this {@code ProcessState}
271      * instance for equality.  Defines equality as having the same
272      * attributes.
273      *
274      * @return {@code true} if and only if the specified object is also
275      * a {@code ProcessState} and both instances have the same
276      * attributes
277      */
278     @Override
279     public boolean
equals(Object o)280     equals(Object o)
281     {
282 	if (o instanceof ProcessState) {
283 	    ProcessState s = (ProcessState)o;
284 	    return ((processID == s.processID) &&
285 		    (state == s.state) &&
286 		    (terminationSignal == s.terminationSignal) &&
287 		    ((terminationSignalName == null) ?
288 		    (s.terminationSignalName == null) :
289 		    terminationSignalName.equals(s.terminationSignalName)) &&
290 		    ((exitStatus == null) ?
291 		    (s.exitStatus == null) :
292 		    exitStatus.equals(s.exitStatus)) &&
293 		    ((message == null) ? (s.message == null) :
294 		    message.equals(s.message)));
295 	}
296 	return false;
297     }
298 
299     /**
300      * Overridden to ensure that equal instances have equal hash codes.
301      */
302     @Override
303     public int
hashCode()304     hashCode()
305     {
306 	int hash = 17;
307 	hash = (37 * hash) + processID;
308 	hash = (37 * hash) + state.hashCode();
309 	hash = (37 * hash) + terminationSignal;
310 	hash = (37 * hash) + (exitStatus == null ? 0 :
311 		exitStatus.hashCode());
312 	hash = (37 * hash) + (message == null ? 0 : message.hashCode());
313 	return hash;
314     }
315 
316     private void
readObject(ObjectInputStream s)317     readObject(ObjectInputStream s)
318             throws IOException, ClassNotFoundException
319     {
320 	s.defaultReadObject();
321 	// check class invariants
322 	try {
323 	    validate();
324 	} catch (Exception e) {
325 	    InvalidObjectException x = new InvalidObjectException(
326 		    e.getMessage());
327 	    x.initCause(e);
328 	    throw x;
329 	}
330     }
331 
332     /**
333      * Gets a string representation of this process state useful for
334      * logging and not intended for display.  The exact details of the
335      * representation are unspecified and subject to change, but the
336      * following format may be regarded as typical:
337      * <pre><code>
338      * class-name[property1 = value1, property2 = value2]
339      * </code></pre>
340      */
341     public String
toString()342     toString()
343     {
344 	StringBuilder buf = new StringBuilder();
345 	buf.append(ProcessState.class.getName());
346 	buf.append("[pid = ");
347 	buf.append(processID);
348 	buf.append(", state = ");
349 	buf.append(state);
350 	buf.append(", terminationSignal = ");
351 	buf.append(terminationSignal);
352 	buf.append(", terminationSignalName = ");
353 	buf.append(terminationSignalName);
354 	buf.append(", exitStatus = ");
355 	buf.append(exitStatus);
356 	buf.append(", message = ");
357 	buf.append(message);
358 	buf.append(']');
359 	return buf.toString();
360     }
361 }
362