blob: f6114d9342141e10d9083df1116523271cf7f8d5 [file] [log] [blame]
Stuart McCullochf3173222012-06-07 21:57:32 +00001package aQute.libg.command;
2
3import java.io.*;
4import java.util.*;
5import java.util.Map.Entry;
6import java.util.concurrent.*;
Stuart McCulloch669423b2012-06-26 16:34:24 +00007import java.util.concurrent.atomic.*;
Stuart McCullochf3173222012-06-07 21:57:32 +00008
Stuart McCulloch669423b2012-06-26 16:34:24 +00009import aQute.lib.io.*;
Stuart McCulloch1a890552012-06-29 19:23:09 +000010import aQute.service.reporter.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000011
12public class Command {
13
14 boolean trace;
15 Reporter reporter;
16 List<String> arguments = new ArrayList<String>();
Stuart McCulloch4482c702012-06-15 13:27:53 +000017 Map<String,String> variables = new LinkedHashMap<String,String>();
Stuart McCullochf3173222012-06-07 21:57:32 +000018 long timeout = 0;
19 File cwd = new File("").getAbsoluteFile();
20 static Timer timer = new Timer(Command.class.getName(), true);
21 Process process;
22 volatile boolean timedout;
23 String fullCommand;
24
25 public Command(String fullCommand) {
26 this.fullCommand = fullCommand;
27 }
28
Stuart McCulloch4482c702012-06-15 13:27:53 +000029 public Command() {}
Stuart McCullochf3173222012-06-07 21:57:32 +000030
31 public int execute(Appendable stdout, Appendable stderr) throws Exception {
32 return execute((InputStream) null, stdout, stderr);
33 }
34
35 public int execute(String input, Appendable stdout, Appendable stderr) throws Exception {
36 InputStream in = new ByteArrayInputStream(input.getBytes("UTF-8"));
37 return execute(in, stdout, stderr);
38 }
39
Stuart McCulloch669423b2012-06-26 16:34:24 +000040 public int execute(final InputStream in, Appendable stdout, Appendable stderr) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +000041 if (reporter != null) {
42 reporter.trace("executing cmd: %s", arguments);
43 }
44
45 String args[] = arguments.toArray(new String[arguments.size()]);
46 String vars[] = new String[variables.size()];
47 int i = 0;
Stuart McCulloch4482c702012-06-15 13:27:53 +000048 for (Entry<String,String> s : variables.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +000049 vars[i++] = s.getKey() + "=" + s.getValue();
50 }
51
52 if (fullCommand == null)
53 process = Runtime.getRuntime().exec(args, vars.length == 0 ? null : vars, cwd);
54 else
55 process = Runtime.getRuntime().exec(fullCommand, vars.length == 0 ? null : vars, cwd);
56
Stuart McCullochf3173222012-06-07 21:57:32 +000057 // Make sure the command will not linger when we go
58 Runnable r = new Runnable() {
59 public void run() {
60 process.destroy();
61 }
62 };
63 Thread hook = new Thread(r, arguments.toString());
64 Runtime.getRuntime().addShutdownHook(hook);
65 TimerTask timer = null;
Stuart McCulloch669423b2012-06-26 16:34:24 +000066 final OutputStream stdin = process.getOutputStream();
67 Thread rdInThread = null;
Stuart McCullochf3173222012-06-07 21:57:32 +000068
69 if (timeout != 0) {
70 timer = new TimerTask() {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000071 //@Override TODO why did this not work? TimerTask implements Runnable
Stuart McCullochf3173222012-06-07 21:57:32 +000072 public void run() {
73 timedout = true;
74 process.destroy();
Stuart McCullochf3173222012-06-07 21:57:32 +000075 }
76 };
77 Command.timer.schedule(timer, timeout);
78 }
79
Stuart McCulloch669423b2012-06-26 16:34:24 +000080 final AtomicBoolean finished = new AtomicBoolean(false);
Stuart McCullochf3173222012-06-07 21:57:32 +000081 InputStream out = process.getInputStream();
82 try {
83 InputStream err = process.getErrorStream();
84 try {
Stuart McCulloch0b639c62012-06-12 12:41:16 +000085 Collector cout = new Collector(out, stdout);
86 cout.start();
87 Collector cerr = new Collector(err, stderr);
88 cerr.start();
Stuart McCulloch4482c702012-06-15 13:27:53 +000089
Stuart McCulloch669423b2012-06-26 16:34:24 +000090 if (in != null) {
91 if (in == System.in) {
92 rdInThread = new Thread("Read Input Thread") {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +000093 @Override
Stuart McCulloch669423b2012-06-26 16:34:24 +000094 public void run() {
95 try {
96 while (!finished.get()) {
97 int n = in.available();
98 if (n == 0) {
99 sleep(100);
100 } else {
101 int c = in.read();
102 if (c < 0) {
103 stdin.close();
104 return;
105 }
106 stdin.write(c);
107 if (c == '\n')
108 stdin.flush();
109 }
110 }
111 }
112 catch (InterruptedIOException e) {
113 // Ignore here
114 }
115 catch (Exception e) {
116 // Who cares?
117 }
118 finally {
119 IO.close(stdin);
120 }
121 }
122 };
123 rdInThread.setDaemon(true);
124 rdInThread.start();
125 } else {
126 IO.copy(in, stdin);
127 stdin.close();
Stuart McCulloch0b639c62012-06-12 12:41:16 +0000128 }
129 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000130 if (reporter != null)
Stuart McCulloch0b639c62012-06-12 12:41:16 +0000131 reporter.trace("exited process");
Stuart McCulloch4482c702012-06-15 13:27:53 +0000132
Stuart McCulloch0b639c62012-06-12 12:41:16 +0000133 cerr.join();
134 cout.join();
135 if (reporter != null)
136 reporter.trace("stdout/stderr streams have finished");
Stuart McCullochf3173222012-06-07 21:57:32 +0000137 }
138 finally {
139 err.close();
140 }
141 }
142 finally {
143 out.close();
144 if (timer != null)
145 timer.cancel();
146 Runtime.getRuntime().removeShutdownHook(hook);
Stuart McCullochf3173222012-06-07 21:57:32 +0000147 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000148
Stuart McCulloch0b639c62012-06-12 12:41:16 +0000149 byte exitValue = (byte) process.waitFor();
Stuart McCulloch669423b2012-06-26 16:34:24 +0000150 finished.set(true);
151 if (rdInThread != null) {
152 if (in != null)
153 IO.close(in);
154 rdInThread.interrupt();
155 }
156
Stuart McCullochf3173222012-06-07 21:57:32 +0000157 if (reporter != null)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000158 reporter.trace("cmd %s executed with result=%d, result: %s/%s, timedout=%s", arguments, exitValue, stdout,
159 stderr, timedout);
Stuart McCullochf3173222012-06-07 21:57:32 +0000160
161 if (timedout)
162 return Integer.MIN_VALUE;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000163
Stuart McCullochf3173222012-06-07 21:57:32 +0000164 return exitValue;
165 }
166
167 public void add(String... args) {
168 for (String arg : args)
169 arguments.add(arg);
170 }
171
172 public void addAll(Collection<String> args) {
173 arguments.addAll(args);
174 }
175
176 public void setTimeout(long duration, TimeUnit unit) {
177 timeout = unit.toMillis(duration);
178 }
179
180 public void setTrace() {
181 this.trace = true;
182 }
183
184 public void setReporter(Reporter reporter) {
185 this.reporter = reporter;
186 }
187
188 public void setCwd(File dir) {
189 if (!dir.isDirectory())
190 throw new IllegalArgumentException("Working directory must be a directory: " + dir);
191
192 this.cwd = dir;
193 }
194
195 public void cancel() {
196 process.destroy();
197 }
198
199 class Collector extends Thread {
200 final InputStream in;
201 final Appendable sb;
202
203 Collector(InputStream inputStream, Appendable sb) {
204 this.in = inputStream;
205 this.sb = sb;
206 setDaemon(true);
207 }
208
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000209 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000210 public void run() {
211 try {
212 int c = in.read();
213 while (c >= 0) {
214 sb.append((char) c);
215 c = in.read();
216 }
217 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000218 catch (IOException e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000219 // We assume the socket is closed
220 }
221 catch (Exception e) {
222 try {
223 sb.append("\n**************************************\n");
224 sb.append(e.toString());
225 sb.append("\n**************************************\n");
226 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000227 catch (IOException e1) {}
Stuart McCullochf3173222012-06-07 21:57:32 +0000228 if (reporter != null) {
229 reporter.trace("cmd exec: %s", e);
230 }
231 }
232 }
233 }
234
Stuart McCullochf3173222012-06-07 21:57:32 +0000235 public Command var(String name, String value) {
236 variables.put(name, value);
237 return this;
238 }
239
240 public Command arg(String... args) {
241 add(args);
242 return this;
243 }
244
245 public Command full(String full) {
246 fullCommand = full;
247 return this;
248 }
249
250 public void inherit() {
251 ProcessBuilder pb = new ProcessBuilder();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000252 for (Entry<String,String> e : pb.environment().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000253 var(e.getKey(), e.getValue());
254 }
255 }
256
257 public String var(String name) {
258 return variables.get(name);
259 }
260
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000261 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000262 public String toString() {
263 StringBuilder sb = new StringBuilder();
264 String del = "";
265
266 for (String argument : arguments) {
267 sb.append(del);
268 sb.append(argument);
269 del = " ";
270 }
271 return sb.toString();
272 }
273}