1 /*
2 * Copyright (c) 1998-2004 The Jgroup Team.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16 *
17 */
18
19 package jgroup.experiment;
20
21 import java.io.BufferedReader;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.util.Timer;
26 import java.util.TimerTask;
27
28 /**
29 * General class for executing shell commands.
30 * @author Bjarte Svaeren
31 * @author Hein Meling
32 */
33 public final class ShellCommand
34 {
35
36 ////////////////////////////////////////////////////////////////////////////////////////////
37 // Fields
38 ////////////////////////////////////////////////////////////////////////////////////////////
39
40 private static final int WAIT_FOR_TIMEOUT = 10000;
41
42 /**
43 * If the process generated output to stdout, this will be set to true.
44 */
45 private volatile boolean stdoutChanged = false;
46
47 /**
48 * If the process generated output to stderr, this will be set to true.
49 */
50 private volatile boolean stderrChanged = false;
51
52 /**
53 * The process object associated with this shell command
54 */
55 private volatile Process process;
56
57 /**
58 * If a process failed during creation, stops blocking on this process
59 * if this boolean is set to true. This is used in waitForProcess(),
60 * and if false, it will block until either the process is created or until
61 * a five second timeout expires, setting this to true.
62 */
63 private volatile boolean timeout = false;
64
65 /**
66 * True if the command output should be suppressed; false otherwise.
67 * This does not suppress stderr outputs.
68 */
69 private volatile boolean suppressCmdOutput = false;
70
71 /**
72 * True if the waitFor method did not yet complete, and a timeout was triggered.
73 * False otherwise.
74 */
75 private static boolean waitForTimeout = false;
76
77
78 ////////////////////////////////////////////////////////////////////////////////////////////
79 // Constructor
80 ////////////////////////////////////////////////////////////////////////////////////////////
81
82 /**
83 * Constructs a shell command object for the given <code>cmd</code>
84 * string. If a thread group is provided, the command will be forked
85 * in a separate thread.
86 *
87 * @param command
88 * The command to be executed.
89 * @param threadGroup
90 * The <code>ThreadGroup</code> the shell command will run in.
91 * If <code>null</code>, the command will run in the current thread.
92 */
93 public ShellCommand(final String cmd, ThreadGroup threadGroup)
94 {
95 this(cmd, threadGroup, false);
96 }
97
98
99 /**
100 * Constructs a shell command object for the given <code>cmd</code>
101 * string. If a thread group is provided, the command will be forked
102 * in a separate thread.
103 *
104 * @param command
105 * The command to be executed.
106 * @param threadGroup
107 * The <code>ThreadGroup</code> the shell command will run in.
108 * If <code>null</code>, the command will run in the current thread.
109 * @param suppressCmdOutput
110 * True will suppress command output.
111 */
112 public ShellCommand(final String cmd, final ThreadGroup threadGroup, boolean suppressCmdOutput)
113 {
114 this.suppressCmdOutput = suppressCmdOutput;
115 if (threadGroup != null) {
116 Thread newThread = new Thread(threadGroup, cmd) {
117 public void run() {
118 try {
119 createProcess(cmd, threadGroup);
120 } catch (IOException e) {
121 e.printStackTrace();
122 System.out.println("FAILED: " + cmd);
123 }
124 }
125 };
126 newThread.start();
127 } else {
128 try {
129 createProcess(cmd, null);
130 } catch (IOException e) {
131 e.printStackTrace();
132 System.out.println("FAILED: " + cmd);
133 }
134 }
135 }
136
137 ////////////////////////////////////////////////////////////////////////////////////////////
138 // Public methods
139 ////////////////////////////////////////////////////////////////////////////////////////////
140
141 public boolean waitForProcess()
142 {
143 TimerTask tt = new TimerTask() {
144 public void run() {
145 timeout = true;
146 }
147 };
148 Timer timer = new Timer("WaitForProcess", true);
149 timer.schedule(tt, 5000);
150 //FIXME this should not use busy waiting.
151 while (process == null && !timeout) { }
152 if (process == null)
153 return false;
154 try {
155 process.waitFor();
156 } catch (InterruptedException e) {
157 process.destroy();
158 }
159 timer.cancel();
160 return true;
161 }
162
163 public boolean stdoutModified()
164 {
165 return stdoutChanged;
166 }
167
168 public boolean stderrModified()
169 {
170 return stderrChanged;
171 }
172
173
174 ////////////////////////////////////////////////////////////////////////////////////////////
175 // Static access methods
176 ////////////////////////////////////////////////////////////////////////////////////////////
177
178 /**
179 * Takes a <code>String</code> and a <code>ThreadGroup</code>
180 * as parameters, and executes the string as a shell command.
181 * A new <code>Thread</code> in the <code>ThreadGroup</code>
182 * given as a parameter, unless the <code>ThreadGroup</code>
183 * parameter is <code>null</code>.
184 * If the parameter is <code>null</code>, the command is run in
185 * the current thread.
186 *
187 * @param command The command to be executed.
188 * @param threadGroup The ThreadGroup the shell command will run in.
189 * If <code>null</code>, the command will run in the current thread.
190 * @throws Exception
191 */
192 public static void exec(final String cmd, ThreadGroup threadGroup)
193 throws IOException
194 {
195 new ShellCommand(cmd, threadGroup);
196 }
197
198
199 /**
200 * Same as exec(commandString, null).
201 * @param command
202 * @throws Exception
203 */
204 public static void exec(final String cmd)
205 throws IOException
206 {
207 exec(cmd, null);
208 }
209
210
211 /**
212 * This methods takes a <code>ThreadGroup</code> as a parameter,
213 * and simply loops until all the threads in the <code>ThreadGroup</code>
214 * have completed their tasks.
215 *
216 * @param threadGroup The <code>ThreadGroup</code> that contain the threads
217 * to wait for.
218 */
219 public static void waitFor(ThreadGroup threadGroup, boolean debug)
220 {
221 int count = threadGroup.activeCount();
222 final Thread curThread = Thread.currentThread();
223 Timer timer = new Timer("WaitForThreadGroup", true);
224 timer.schedule(new TimerTask() {
225 public void run() {
226 waitForTimeout = true;
227 curThread.interrupt();
228 }
229 }, WAIT_FOR_TIMEOUT*count);
230
231 // Loop until all threads have finished
232 while (count > 0 && !waitForTimeout) {
233 try {
234 Thread.sleep(2000);
235 } catch (InterruptedException e) {
236 /*
237 * This means that the timer above was triggered and we should
238 * abort the waiting; that is the waitForTimeout=true. This will
239 * read the active thread count again, and exit the while loop.
240 */
241 }
242 count = threadGroup.activeCount();
243 }
244 if (count > 0) {
245 // Interrupt the remaining threads in an attempt to continue
246 threadGroup.interrupt();
247 if (debug) {
248 System.out.println("Remaining threads: " + count);
249 threadGroup.list();
250 }
251 }
252 timer.cancel();
253 waitForTimeout = false;
254 }
255
256 public static void waitFor(ThreadGroup threadGroup)
257 {
258 waitFor(threadGroup, false);
259 }
260
261
262 ////////////////////////////////////////////////////////////////////////////////////////////
263 // Private methods
264 ////////////////////////////////////////////////////////////////////////////////////////////
265
266 private void createProcess(final String cmd, ThreadGroup threadGroup)
267 throws IOException
268 {
269 stdoutChanged = false;
270 stderrChanged = false;
271 process = Runtime.getRuntime().exec(cmd);
272 if (!suppressCmdOutput)
273 System.out.println(cmd);
274 final String scmd = cmd.split(" ")[0];
275
276 Thread outThread = new Thread(scmd + "-out") {
277 public void run() {
278 InputStream processOut = process.getInputStream();
279 BufferedReader out = new BufferedReader(new InputStreamReader(processOut));
280 String stdout;
281
282 try {
283 while ((stdout = out.readLine()) != null) {
284 synchronized(this) {
285 stdoutChanged = true;
286 System.out.println(scmd + "-out: " + stdout);
287 }
288 }
289 out.close();
290 } catch (java.io.IOException e) {
291 e.printStackTrace();
292 System.out.println(cmd + " failed");
293 }
294 }
295 };
296
297 Thread errorThread = new Thread(scmd + "-err") {
298 public void run() {
299 InputStream processError = process.getErrorStream();
300 BufferedReader err = new BufferedReader(new InputStreamReader(processError));
301 String stderr;
302
303 try {
304 while ((stderr = err.readLine()) != null) {
305 synchronized(this) {
306 stderrChanged = true;
307 System.out.println(cmd);
308 System.out.println(scmd + "-err: " + stderr);
309 }
310 }
311 err.close();
312 } catch (java.io.IOException e) {
313 System.out.println(cmd + " failed");
314 }
315 }
316 };
317
318 outThread.start();
319 errorThread.start();
320 }
321 }